import os
import sqlite3
import logging
import logging.handlers
import cPickle

import  wx
from wx.lib.masked import TimeCtrl
import wx.lib.scrolledpanel as scrolled
import  wx.grid as  gridlib

from genutils.list_dict import List_dict
from genutils.UTCtoJDN import UTCtoJDN
from genutils.io import from_config
from stimuli.helper_codes.general_stimulus import setup_window

#import gc
#gc.set_debug(gc.DEBUG_LEAK)

debug_mode = False # used to make stimuli go into preview mode.
debug_save_mode = True # save data for dev.
win_size = [1440,900] # a one stop shop for setting the resolution FIXME



# ===========================================================================================================
#                                                 Spec_panel
# ===========================================================================================================
class Spec_panel(wx.Panel):
    '''
    This is the specimin panel, it collects information about the experimental specimin prep.
    '''
    def __init__(self, parent, id, **kwargs):
        wx.Panel.__init__(self, parent, id, **kwargs)
        self.parent = parent
        self.logger = logging.getLogger('Run_experiment.Spec_panel')
        self.current_id = 0

    def clear_everything(self):
        """
        Clear everything in this panel so we can start fresh.
        """
        if self.GetSizer() is not None:
            self.GetSizer().DeleteWindows()
        
    def setup_ui(self):
        """
        Setup the ui elements, but don't put any values in any fields.
        """
        description = wx.StaticText(self, wx.ID_ANY,
                          "Specify information about this particular experimental specimin.")

        # ----------------------
        # surgery information
        # ----------------------
        surg_panel = wx.Panel(self, wx.ID_ANY, style=wx.TAB_TRAVERSAL|wx.BORDER_SUNKEN, 
                              name='Surgery Info')
        surg_text = wx.StaticText(surg_panel, wx.ID_ANY, "Surgery Info")
        # date
        label_date = wx.StaticText(surg_panel, wx.ID_ANY, "Date:")
        date_picker = wx.DatePickerCtrl(surg_panel, size=(120,-1),
                                        style = wx.DP_DROPDOWN    |
                                                wx.DP_SHOWCENTURY |
                                                wx.DP_ALLOWNONE    ) 
        # begin time
        label_begin_time = wx.StaticText(surg_panel, wx.ID_ANY, "Begin Time:")
        begin_time_picker = TimeCtrl(surg_panel, wx.ID_ANY)
        begin_time_picker.SetFormat('HHMM')
        # end time
        label_end_time = wx.StaticText(surg_panel, wx.ID_ANY, "End Time:")
        end_time_picker = TimeCtrl(surg_panel, wx.ID_ANY)
        end_time_picker.SetFormat('HHMM')

        # SURGERY PANEL LAYOUT
        surg_sizer = wx.GridBagSizer(hgap=3, vgap=3)
        surg_sizer.Add(surg_text,         pos=(0,0), border=10, span=(1,2),
                                           flag=wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL)
        surg_sizer.Add(label_date,        pos=(1,0), border=10, 
                                           flag=wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
        surg_sizer.Add(date_picker,       pos=(1,1), border=10, 
                                           flag=wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL)
        surg_sizer.Add(label_begin_time,  pos=(2,0), border=10, 
                                           flag=wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
        surg_sizer.Add(begin_time_picker, pos=(2,1), border=10, 
                                           flag=wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL)
        surg_sizer.Add(label_end_time,    pos=(3,0), border=10, 
                                           flag=wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
        surg_sizer.Add(end_time_picker,   pos=(3,1), border=10, 
                                           flag=wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL)
        surg_panel.SetSizer(surg_sizer)

        # ----------------------
        # Eye Cup Information
        # ----------------------
        eyec_panel = wx.Panel(self, wx.ID_ANY, style=wx.TAB_TRAVERSAL|wx.BORDER_SUNKEN, 
                              name='Eye Cup Info')
        eyec_text = wx.StaticText(eyec_panel, wx.ID_ANY, "Eye Cup Info")
        # which eye was prepared?
        eye_choices = ['Left', 'Right', 'Both']
        label_wewp = wx.StaticText(eyec_panel, wx.ID_ANY, "Which eye was prepared?")
        wewp_radio_box = wx.RadioBox(eyec_panel, wx.ID_ANY, choices=eye_choices,
                                     style=wx.RA_VERTICAL, name='Which eye was prepared?')
        # abnormalities?
        abno_check_box = wx.CheckBox(eyec_panel, wx.ID_ANY, label="Abnormalities")
        abno_text_ctrl = wx.TextCtrl(eyec_panel, wx.ID_ANY, size=(150,-1))
        abno_text_ctrl.Disable()
        self.Bind(wx.EVT_CHECKBOX, self.OnAbnormalities, abno_check_box)

        # notes
        note_text = wx.StaticText(self, wx.ID_ANY, "Specimin Notes:")
        note_text_ctrl = wx.TextCtrl(self, wx.ID_ANY, size=(500,200),
                                     style=wx.TE_MULTILINE )
        # submit panel
        subm_panel = wx.Panel(self, wx.ID_ANY)
        # submit button
        submit_button = wx.Button(subm_panel, wx.ID_ANY, "Submit")
        self.Bind(wx.EVT_BUTTON, self.OnSubmit, submit_button)
        # prev button
        prev_button = wx.Button(subm_panel, wx.ID_ANY, "<", size=(30,-1))
        self.Bind(wx.EVT_BUTTON, self.OnPrev, prev_button)
        # next button
        next_button = wx.Button(subm_panel, wx.ID_ANY, ">", size=(30,-1))
        self.Bind(wx.EVT_BUTTON, self.OnNext, next_button)
        subm_sizer = wx.GridBagSizer(hgap=3,vgap=3)
        subm_sizer.Add(prev_button, pos=(0,0))
        subm_sizer.Add(submit_button, pos=(0,1))
        subm_sizer.Add(next_button, pos=(0,2))
        subm_sizer.Add(wx.StaticText(self, wx.ID_ANY, ""), pos=(1,3))
        subm_panel.SetSizer(subm_sizer)
        
        # EYE CUP PANEL LAYOUT
        eyec_sizer = wx.GridBagSizer(hgap=3, vgap=3)
        eyec_sizer.Add(eyec_text,         pos=(0,0), border=10, span=(1,2),
                                           flag=wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL)
        eyec_sizer.Add(label_wewp,        pos=(1,0), border=10, 
                                           flag=wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL)
        eyec_sizer.Add(wewp_radio_box,    pos=(1,1), border=10,
                                           flag=wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL)
        eyec_sizer.Add(abno_check_box,    pos=(2,0), border=10,
                                           flag=wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
        eyec_sizer.Add(abno_text_ctrl,    pos=(2,1), border=10,
                                           flag=wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL)
        eyec_panel.SetSizer(eyec_sizer)

        # Set up the sizers
        page_sizer = wx.GridBagSizer(hgap=5, vgap=5)
        
        # add items to sizers
        # -- main page sizer
        page_sizer.Add(description, pos=(0,1), span=(1,2),
                                     flag=wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL)
        page_sizer.Add(surg_panel,  pos=(2,1), 
                                     flag=wx.ALIGN_RIGHT)
        page_sizer.Add(eyec_panel,  pos=(2,2), 
                                     flag=wx.ALIGN_LEFT)
        page_sizer.Add(note_text,   pos=(3,1), flag=wx.ALIGN_LEFT)
        page_sizer.Add(note_text_ctrl, pos=(4,1), span=(1,2))
        page_sizer.Add(subm_panel,    pos=(5,1) )
        self.SetSizer(page_sizer)
        page_sizer.Layout() # needed to update the screen after a self.clear_everything

        # keep track of things who's values we care about later.
        self.date_picker       = date_picker
        self.begin_time_picker = begin_time_picker
        self.end_time_picker   = end_time_picker
        self.wewp_radio_box    = wewp_radio_box
        self.abno_check_box    = abno_check_box
        self.abno_text_ctrl    = abno_text_ctrl
        self.note_text_ctrl    = note_text_ctrl
        self.next_button       = next_button
        self.prev_button       = prev_button

    def reset_everything_to_database(self):
        """
        Restore everything on this page back to the way it was based on the current_ids
        """
        self.set_everything_from_database(self.current_id)
        
    def set_everything_from_database(self, id):
        """
        Set everything on this page from the database with id supplied.
            If id == 0 then clear all entries.
            if id == -1 then make id the max id in the database
        """
        spec_id = id
        self.clear_everything()
        self.setup_ui()
        if spec_id == 0:
            self.logger.debug("Setting up specimin panel with spec_id=0 (clean).")
            self.current_id = 0
        elif spec_id == -1:
            # find the database entry with highest spec_id
            dbcur.execute( "SELECT MAX(spec_id) FROM specimin" )
            spec_id, = dbcur.fetchone()
            if spec_id is not None:
                self.current_id = spec_id
                self.set_everything( *self.get_values_from_database(self.current_id) )
                self.logger.info('Found database entry with highest spec_id=%d' % self.current_id)
            else:
                self.logger.info('No database entries in the specimin table.')
        else:
            # just set up the page with values corresponding to the spec_id passed.
            self.current_id = spec_id
            self.set_everything( *self.get_values_from_database(self.current_id) )
            self.logger.debug('Set up specimin panel with spec_id=%d' % self.current_id)
             
        self.parent.page[1]['panel_is_setup'] = False
        self.parent.page[2]['panel_is_setup'] = False
        self.parent.page[3]['panel_is_setup'] = False
        self.manage_prev_next_state()

    def get_values_from_database(self, spec_id):
        # return the set of values from the database for the specified specimin id
        self.logger.debug('Fetching information about specimin spec_id=%d' % spec_id)
        dbcur.execute(
            "select date, begin_time, end_time, wewp, abnormalities, note from specimin where spec_id=?",
            (spec_id,))
        rows = dbcur.fetchall()
        if len(rows) > 0:
            return rows[0]
        else:
            raise RuntimeError("The spec_id you specified was not valid: %d" % (spec_id))
        
    def set_everything(self, date, begin_time, end_time, wewp, abnormalities, note):
        """
        This is just a helper function for set_everything_from_database and should not be called directly.
        """
        # set everything given the values passed
        self.logger.debug('Setting everything with values passed.')
        # dates and times should be in JDN float format.
        ddt = wx.DateTime() # dummy date time for conversions
        ddt.SetJDN(date+0.00000001) # small number added, to fix rounding error
        self.date_picker.SetValue(ddt)
        ddt.SetJDN(begin_time+0.00000001)
        self.begin_time_picker.SetValue(ddt)
        ddt.SetJDN(end_time+0.00000001)
        self.end_time_picker.SetValue(ddt)
        self.wewp_radio_box.SetStringSelection(wewp)
        # set abnormalities.
        if abnormalities != None:
            self.abno_check_box.SetValue(True)
            self.abno_text_ctrl.SetValue(abnormalities)
            self.abno_text_ctrl.Enable()
        else: # nothing to set.
            self.abno_check_box.SetValue(False)
            self.abno_text_ctrl.SetValue('')
            self.abno_text_ctrl.Disable()
        self.note_text_ctrl.SetValue(note)

    def OnAbnormalities(self, event):
        self.logger.debug('Abnormalities = %s' % event.IsChecked())
        if event.IsChecked():
            self.abno_text_ctrl.Enable()
        else:
            self.abno_text_ctrl.Disable()

    def OnPrev(self, event):
        ''' if possible, set all values in this panel to previous entry in database '''
        self.logger.debug('Previous button pressed')
        # show dialog if anything has changed vs. database
        if self.panel_is_altered():
            dlg = Altered_panel_confirmation_dialog(self, wx.ID_ANY)
            dlg.ShowModal()
            if self.action_choice == 'Continue':
                # will change to
                psid = self.prev_spec_id
                self.set_everything_from_database(psid)
                self.logger.info("Updated specimin panel to database spec_id=%d" % (psid))
            else:
                return # do nothing if user canceled action
        else: # no changes detected
            # will change to
            psid = self.prev_spec_id
            self.set_everything_from_database(psid)
            self.logger.info("Updated specimin panel to database spec_id=%d" % (psid))

    def OnNext(self, event):
        ''' If possible, set all values in this panel to NEXT entry in database. '''
        self.logger.debug('Next button pressed')
        if self.panel_is_altered():
            dlg = Altered_panel_confirmation_dialog(self, wx.ID_ANY)
            dlg.ShowModal()
            if self.action_choice == 'Continue':
                # will change to
                nsid = self.next_spec_id
                self.set_everything_from_database(nsid)
                self.logger.info("Updated specimin panel to database spec_id=%d" % (nsid))
            else:
                return # do nothing if user canceled action
        else: # no changes detected
            # will change to
            nsid = self.next_spec_id
            self.set_everything_from_database(nsid)
            self.logger.info("Updated specimin panel to database spec_id=%d" % (nsid))

    def manage_prev_next_state(self):
        ''' determine the enable/disabled state of previous and next buttons. '''
        self.logger.debug('Managing previous/next buttons state')
        # determine the max spec_id in the database
        dbcur.execute( "SELECT spec_id FROM specimin" )
        rows = dbcur.fetchall()
        if len(rows) > 0:
            spec_ids = [row[0] for row in rows]
            spec_ids.sort()
            self.logger.debug("The list of spec_ids is: %s" % spec_ids)
            if self.current_id != 0:
                current_spec_id_index = spec_ids.index(self.current_id)
                self.logger.debug('The current_spec_id is index %d in the list of spec_ids' % current_spec_id_index)
                # check above the current_spec_id_index
                if current_spec_id_index <= len(spec_ids)-2:
                    self.next_spec_id = spec_ids[current_spec_id_index+1]
                    self.next_button.Enable()
                    self.logger.debug('Enabling the next button with next_spec_id=%d'% self.next_spec_id)
                else:
                    self.next_spec_id = 0
                    self.next_button.Disable()
                # check below the current_spec_id_index
                if current_spec_id_index >= 1:
                    self.prev_spec_id = spec_ids[current_spec_id_index-1]
                    self.prev_button.Enable()
                    self.logger.debug('Enabling the prev button with prev_spec_id=%d'% self.prev_spec_id)
                else:
                    self.prev_spec_id = 0
                    self.prev_button.Disable()
            else:
                # we cannot get anything useful from spec_ids.index(self.current_id)
                self.prev_spec_id = 0
                self.next_spec_id = spec_ids[0]
                self.prev_button.Disable()
                self.next_button.Enable()
        else:
            self.logger.debug('Found no specimin entries in database.')
            self.prev_spec_id = 0
            self.next_spec_id = 0
            self.prev_button.Disable()
            self.next_button.Disable()
            
    def OnSubmit(self, event):
        # collect information from all the things on this page.
        self.logger.debug('Submitting data to the database.')
        # the date is retrieved, then convereted to JDN format
        specimin_date          = self.date_picker.GetValue().GetJDN()
        # the time is retrieved in wx.DateTime format and then converted to JDN
        specimin_begin_time    = self.begin_time_picker.GetWxDateTime().GetJDN()
        specimin_end_time      = self.end_time_picker.GetWxDateTime().GetJDN()
        specimin_wewp          = self.wewp_radio_box.GetStringSelection()
        if self.abno_check_box.IsChecked():
            specimin_abnormalities = self.abno_text_ctrl.GetValue()
        else:
            specimin_abnormalities = None
        specimin_note          = self.note_text_ctrl.GetValue()
        # insert these values into the database.
        dbcur.execute(
         "INSERT INTO specimin (Date, Begin_time, End_time, wewp, Abnormalities, Note) VALUES (?,?,?,?,?,?)",
          (specimin_date, specimin_begin_time, specimin_end_time, specimin_wewp, specimin_abnormalities, specimin_note))

        # determine the max spec_id in the database
        dbcur.execute( "SELECT MAX(spec_id) FROM specimin" )
        self.current_id, = dbcur.fetchone()
        self.logger.info('Submitted specimin information to the database, new spec_id=%d' % self.current_id)
        # make changes to database perminant
        if not debug_mode:
            dbcon.commit()
            self.logger.info('Committed all changes to the database %s/%s.' % (database_path, database_name) )

        self.parent.page[1]['panel_is_setup'] = False
        self.parent.page[2]['panel_is_setup'] = False
        self.parent.page[3]['panel_is_setup'] = False
        self.manage_prev_next_state()

    def panel_is_altered(self):
        """
        Returns False if the panel is exactly in line with the database entry for self.current_id
        Returns True otherwise.
        """

        # the date is retrieved, then convereted to JDN format
        specimin_date          = self.date_picker.GetValue().GetJDN()
        # the time is retrieved in wx.DateTime format and then converted to JDN
        specimin_begin_time    = self.begin_time_picker.GetWxDateTime().GetJDN()
        specimin_end_time      = self.end_time_picker.GetWxDateTime().GetJDN()
        specimin_wewp          = self.wewp_radio_box.GetStringSelection()
        if self.abno_check_box.IsChecked():
            specimin_abnormalities = self.abno_text_ctrl.GetValue()
        else:
            specimin_abnormalities = None
        specimin_note          = self.note_text_ctrl.GetValue()

        # get the values from the database for this self.current_id
        if self.current_id != 0:
            date, begin_time, end_time, wewp, abnormalities, note = self.get_values_from_database(self.current_id)
        else: # if self.current_id == 0 then we definately haven't hit the submit, button and should.
            self.logger.info("There are no entries in the specimin table, you MUST submit something before progressing.")
            return True

        # is the date different?
        if date != specimin_date:
            self.logger.info('The date entered does not match database entry for spec_id=%d. PREV(%f) NOW(%f) JDN format.' 
                             % (self.current_id, date, specimin_date) )
            return True

        # is the begin_time different?
        if begin_time != specimin_begin_time:
            self.logger.info('The begin_time entered does not match database entry for spec_id=%d. PREV(%f) NOW(%f) JDN format.' 
                             % (self.current_id, begin_time, specimin_begin_time) )
            return True

        # is the end_time different?
        if end_time != specimin_end_time:
            self.logger.info('The end_time entered does not match database entry for spec_id=%d. PREV(%f) NOW(%f) JDN format.' 
                             % (self.current_id, end_time, specimin_end_time) )
            return True

        # is the wewp different?
        if wewp != specimin_wewp:
            self.logger.info('The wewp (what eye was prepared) entered does not match database entry for spec_id=%d. PREV(%s) NOW(%s)' 
                             % (self.current_id, wewp, specimin_wewp) )
            return True

        # is the abnormalities different?
        if abnormalities != specimin_abnormalities:
            self.logger.info('The abnormalities entered does not match database entry for spec_id=%d.  PREV(%s), NOW(%s)' 
                             % (self.current_id, abnormalities, specimin_abnormalities) )
            return True

        # is the note different?
        if note != specimin_note:
            self.logger.info('The note entered does not match database entry for spec_id=%d.  PREV(%s) NOW(%s)' 
                              % (self.current_id, note, specimin_note) )
            return True

        # all checks passed so panel is not altered...
        return False

# ===========================================================================================================
#                                                 Electrode
# ===========================================================================================================
class Electrode(wx.Panel):
    '''
    This is an electrode panel, it collects information about one electrode.
    '''
    def __init__(self, parent, id, electrode_number, *args, **kwargs):
        wx.Panel.__init__(self, parent, id, *args, **kwargs)
        self.parent = parent
        self.logger = logging.getLogger('Run_experiment.Sess_panel.Elec_panel.Electrode')
        
        # type
        type_text = wx.StaticText(self, wx.ID_ANY, "Type: ")
        type_choice = wx.Choice(self, wx.ID_ANY, choices = ['Recording','Stimulating'])
        self.Bind(wx.EVT_CHOICE, self.OnType, type_choice)
        self.type = 'Recording'
        # checkbox
        self.cb = wx.CheckBox(self, wx.ID_ANY)
        self.Bind(wx.EVT_CHECKBOX, self.OnCheck, self.cb)
        # trodes
        trodes_text = wx.StaticText(self, wx.ID_ANY, "# of Trodes: ")
        trodes_spin_ctrl = wx.SpinCtrl(self, wx.ID_ANY)
        trodes_spin_ctrl.SetRange(1,1000)
        trodes_spin_ctrl.SetValue(1)
        # resistance
        res_text      = wx.StaticText(self, wx.ID_ANY, "Resistance: ")
        res_text_ctrl = wx.TextCtrl(self, wx.ID_ANY, size=(100,-1))
        res_post_text = wx.StaticText(self, wx.ID_ANY, " k Ohms")
        # Location
        loc_text      = wx.StaticText(self, wx.ID_ANY, "Location: ")
        loc_text_ctrl = wx.TextCtrl(self, wx.ID_ANY, size=(100,-1))
        loc_post_text = wx.StaticText(self, wx.ID_ANY, " (IMC, IPC, ...)")
        # Depth
        dep_text      = wx.StaticText(self, wx.ID_ANY, "Depth: ")
        dep_text_ctrl = wx.TextCtrl(self, wx.ID_ANY, size=(100,-1))
        dep_post_text = wx.StaticText(self, wx.ID_ANY, " um")
        # Note
        note_text      = wx.StaticText(self, wx.ID_ANY, "Note: ")
        note_text_ctrl = wx.TextCtrl(self, wx.ID_ANY, size=(160,-1))
        
        

        elec_sizer = wx.GridBagSizer(hgap=3, vgap=3)
        elec_sizer.Add(type_text,        pos=(0,1), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT)
        elec_sizer.Add(type_choice,      pos=(0,2), span=(1,2), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
        elec_sizer.Add(trodes_text,      pos=(1,1), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT)
        elec_sizer.Add(trodes_spin_ctrl, pos=(1,2), span=(1,2), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
        elec_sizer.Add(res_text,         pos=(2,1), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT)
        elec_sizer.Add(res_text_ctrl,    pos=(2,2), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
        elec_sizer.Add(res_post_text,    pos=(2,3), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
        elec_sizer.Add(loc_text,         pos=(3,1), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT)
        elec_sizer.Add(loc_text_ctrl,    pos=(3,2), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
        elec_sizer.Add(loc_post_text,    pos=(3,3), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
        elec_sizer.Add(dep_text,         pos=(4,1), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT)
        elec_sizer.Add(dep_text_ctrl,    pos=(4,2), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
        elec_sizer.Add(dep_post_text,    pos=(4,3), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
        elec_sizer.Add(note_text,        pos=(5,1), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT)
        elec_sizer.Add(note_text_ctrl,   pos=(5,2), span=(1,2), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
        elec_sizer.Add(wx.StaticText(self), pos=(6,1))
        self.SetSizer(elec_sizer)

        # keep track of controls.
        self.type_choice      = type_choice
        self.trodes_spin_ctrl = trodes_spin_ctrl
        self.res_text_ctrl    = res_text_ctrl
        self.loc_text_ctrl    = loc_text_ctrl
        self.dep_text_ctrl    = dep_text_ctrl
        self.note_text_ctrl   = note_text_ctrl

    def __eq__(self, other):
        self_values = self.get_values()
        other_values = other.get_values()
        # invisible electrodes are equal to everything (odd convention but works)
        if not self.visible or not other.visible:
            return True
        for sval, oval in zip(self_values, other_values):
            if sval != oval:
                return False
        return True
    def __ne__(self, other):
        return not self == other

    def disable_everything(self):
        self.type_choice.Disable()
        self.trodes_spin_ctrl.Disable()
        self.res_text_ctrl.Disable()
        self.loc_text_ctrl.Disable()
        self.dep_text_ctrl.Disable()
        self.note_text_ctrl.Disable()
        self.cb.Disable()

    def OnCheck(self, event):
        self.logger.debug('Is checked = %s' % event.IsChecked())
        if event.IsChecked():
            self.make_visible()
        else:
            self.make_invisible()

    def OnType(self, event):
        self.type = event.GetString()
        self.logger.debug('Setting type to %s' % self.type)
        self.parent.GetParent().calc_types()

    def make_invisible(self):
        self.logger.debug('Making invisible')
        self.SetTransparent(80)
        self.SetBackgroundColour(wx.Colour(210,190,190))
        self.visible=False
        self.cb.SetValue(False)
        self.parent.GetParent().calc_types()

    def make_visible(self):
        self.logger.debug('Making visible')
        self.SetTransparent(255)
        self.SetBackgroundColour(wx.Colour(190,210,190))
        self.visible=True
        self.cb.SetValue(True)
        self.parent.GetParent().calc_types()

    def set_values(self, type, trodes, resistance, location, depth, note):
        '''
        Set all the electrode values.  Use make_visible and make_invisible to set checkbox.
        '''
        self.logger.debug('Setting values')
        # type
        self.type_choice.SetStringSelection(type)
        self.type = type
        # trodes
        self.trodes_spin_ctrl.SetValue(trodes)
        # resistance, location, depth, and note
        self.res_text_ctrl.SetValue(str(resistance))
        self.loc_text_ctrl.SetValue(str(location))
        self.dep_text_ctrl.SetValue(str(depth))
        self.note_text_ctrl.SetValue(str(note))

    def get_values(self):
        '''
        Returns all the values associated with an electrode.
        '''
        # get the values, then return them
        type       = self.type_choice.GetStringSelection()
        trodes     = self.trodes_spin_ctrl.GetValue()
        resistance = self.res_text_ctrl.GetValue()
        location   = self.loc_text_ctrl.GetValue()
        depth      = self.dep_text_ctrl.GetValue()
        note       = self.note_text_ctrl.GetValue()
        return type, trodes, resistance, location, depth, note

class Elec_panel(wx.Panel):
    '''
    This is the electrode info panel.  It collects all the information about all electrodes.
    '''
    def __init__(self, parent, id, *args, **kwargs):
        wx.Panel.__init__(self, parent, id, *args, **kwargs)
        self.logger = logging.getLogger('Run_experiment.Sess_panel.Elec_panel')
        
        self.elec_panel_description = wx.StaticText(self, wx.ID_ANY, 'Electrode(s):  Rec = %d   Stim = %d ' % 
                                             (0,0) )

        elec_add_button = wx.Button(self, wx.ID_ANY, "Add Electrode")
        self.elec_add_button = elec_add_button
        self.Bind(wx.EVT_BUTTON, self.OnAddElec, elec_add_button)
        elec_reorder_button = wx.Button(self, wx.ID_ANY, "Reorder List")
        self.elec_reorder_button = elec_reorder_button
        self.Bind(wx.EVT_BUTTON, self.OnReorder, elec_reorder_button)
        self.sp = scrolled.ScrolledPanel(self, wx.ID_ANY, size=(435,207), style=wx.TAB_TRAVERSAL|wx.BORDER_SUNKEN)
        self.sp.SetupScrolling(scroll_y=False)
        # make an electrode in the elec_panel
        self.electrodes = []

        # lay out the sp
        self.sp_gbs = wx.GridBagSizer(hgap=3, vgap=3)
        self.sp.SetSizer(self.sp_gbs)

        # elec_panel layout
        gbs = wx.GridBagSizer(hgap=3, vgap=3)
        gbs.Add(self.elec_panel_description, pos=(0,0), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
        gbs.Add(elec_reorder_button, pos=(0,1), flag=wx.ALIGN_RIGHT)
        gbs.Add(elec_add_button, pos=(0,2), flag=wx.ALIGN_RIGHT)
        gbs.Add(self.sp, pos=(1,0), span=(1,3))
        self.SetSizer(gbs)

    def __eq__(self, other):
        # make sure there are the same number of visible electrodes
        self_visible_electrodes = 0
        for elec in self.electrodes:
            if elec.visible:
                self_visible_electrodes += 1
        other_visible_electrodes = 0
        for elec in other.electrodes:
            if elec.visible:
                other_visible_electrodes += 1
        if self_visible_electrodes != other_visible_electrodes:
            return False
        for selec in self.electrodes:
            self_found_in_other = False
            for oelec in other.electrodes:
                if selec == oelec:
                    self_found_in_other = True
            if not self_found_in_other:
                return False
        return True

    def __ne__(self, other):
        return not self == other

    def set_everything_from_database(self, dbcur, sess_id):
        dbcur.execute("""SELECT type, trodes, resistance, location, depth, note 
                         FROM electrode WHERE sess_id = ?""", (sess_id,))
        self.clear_electrodes()
        for row in dbcur:
            self.add_electrode(*row)

    def disable_everything(self):
        self.elec_add_button.Disable()
        self.elec_reorder_button.Disable()
        for electrode in self.electrodes:
            electrode.disable_everything()

    def add_electrode(self, type, trodes, resistance, location, depth, note):
        '''
        Add an electrode, and fill it with these values.
        '''
        self.logger.debug('Adding an electrode with values supplied.')
        self.OnAddElec( None )
        self.electrodes[-1].set_values(type, trodes, resistance, location, depth, note)
        self.calc_types()

    def OnAddElec(self, event):
        self.logger.debug('Creating new electrode.')
        self.electrodes.append(Electrode(self.sp, wx.ID_ANY, len(self.electrodes)+1,
                               style=wx.BORDER_RAISED|wx.DOUBLE_BORDER))
        self.sp_gbs.Add(self.electrodes[-1], pos=(0, len(self.electrodes)-1) )
        self.sp_gbs.Layout()
        self.sp.SetupScrolling(scroll_y=False, scrollToTop=False)
        self.electrodes[-1].make_visible()

    def clear_electrodes(self):
        '''
        Clear all electrodes from this panel.
        '''
        self.logger.debug('Clearing all electrodes.')
        for i in xrange(len(self.electrodes)):
            self.sp_gbs.Remove(self.electrodes[i])
            self.electrodes[i].Destroy()
            
        self.electrodes = []
        self.reorder_electrodes()
        
    def is_correct_order(self, electrodes):
        # if any visibles follow an invisible, return false, otherwise return true
        self.logger.debug('Checking electrode order.')
        found_invisible = False
        for e in electrodes:
            if e.visible and found_invisible:
                return False
            if not e.visible:
                found_invisible = True
        return True
        
    def OnReorder(self, event):
        self.logger.debug('Reorder button pressed.')
        self.reorder_electrodes()

    def reorder_electrodes(self):
        self.logger.debug('Reordering electrodes.')
        while not self.is_correct_order(self.electrodes):
            # find the last visible electrode who has an invisible electrode before it.
            lve = 0 # assume no visible electrodes
            pie = lve
            search_list = [len(self.electrodes)-1-i for i in xrange(len(self.electrodes))]
            #print "searching in " + str(search_list)
            for i in xrange(len(search_list)-1):
                #print "checking positions %d and %d" % (search_list[i], search_list[i+1])
                if self.electrodes[search_list[i]].visible and \
                   not self.electrodes[search_list[i+1]].visible:
                    lve = search_list[i] # if we find one, keep its index
                    pie = search_list[i+1]
                    break
            #print "found positions %d and %d" % (pie, lve)
            # swap pie and lve if they are not equal
            if pie != lve:
                #print "swaping positions %d and %d" % (pie, lve)
                self.sp_gbs.SetItemPosition(self.electrodes[lve],   (0,len(self.electrodes)+2) )
                self.sp_gbs.SetItemPosition(self.electrodes[pie],   (0,lve) )
                self.sp_gbs.SetItemPosition(self.electrodes[lve],   (0,pie) )
                # swap their positions in the list as well
                swap = self.electrodes[pie]
                self.electrodes[pie]   = self.electrodes[lve]
                self.electrodes[lve]   = swap
        self.sp_gbs.Layout()
        self.sp.SetupScrolling(scroll_y=False, scrollToTop=False)


    def calc_types(self):
        # count up how many of each type of electrode has been chosen
        self.logger.debug('Counting up electrode types.')
        types = {}
        types['Recording']   = 0
        types['Stimulating'] = 0
        for electrode in self.electrodes:
            if electrode.visible:
                types[electrode.type] += 1
        # update the description string.
        self.elec_panel_description.SetLabel( 'Electrode(s):  Rec = %d   Stim = %d ' % 
                                             (types['Recording'], types['Stimulating']) )

    def get_values(self):
        '''
        Get the values for all electrodes which are set to visible.
        '''
        self.logger.debug('Fetching values from all electrodes.')
        types       = []
        trodes      = []
        resistances = []
        locations   = []
        depths      = []
        notes       = []
        
        # get all the electrodes information
        for electrode in self.electrodes:
            if electrode.visible:
                ty, tr, re, lo, de, no = electrode.get_values()
                types.append(ty)
                trodes.append(tr)
                resistances.append(re)
                locations.append(lo)
                depths.append(de)
                notes.append(no)

        return types, trodes, resistances, locations, depths, notes
        
        


# ===========================================================================================================
#                                                 Drug
# ===========================================================================================================
class Drug(wx.Panel):
    '''
    This is an electrode panel, it collects information about one electrode.
    '''
    def __init__(self, parent, id, electrode_number, *args, **kwargs):
        wx.Panel.__init__(self, parent, id, *args, **kwargs)
        self.logger = logging.getLogger('Run_experiment.Sess_panel.Drug_panel.Drug')
        self.parent = parent
        
        # checkbox
        self.cb = wx.CheckBox(self, wx.ID_ANY)
        self.Bind(wx.EVT_CHECKBOX, self.OnCheck, self.cb)

        # name
        name_text      = wx.StaticText(self, wx.ID_ANY, "Name: ")
        name_text_ctrl = wx.TextCtrl(self, wx.ID_ANY, size=(160,-1))

        # concentration
        conc_text      = wx.StaticText(self, wx.ID_ANY, "Concentration: ")
        conc_text_ctrl = wx.TextCtrl(self, wx.ID_ANY, size=(60,-1))
        conc_post_text = wx.StaticText(self, wx.ID_ANY, "mM")

        # time applied
        time_text      = wx.StaticText(self, wx.ID_ANY, "Time Applied: ")
        time_picker    = TimeCtrl(self, wx.ID_ANY)
        time_picker.SetFormat('HHMM')

        # location
        loc_text = wx.StaticText(self, wx.ID_ANY, "Location: ")
        location_list = ['Bath', "Intracellular:", "Local:", "Eye cup:"]
        loc_choice   = wx.Choice(self, wx.ID_ANY, choices=location_list)
        self.Bind(wx.EVT_CHOICE, self.OnLocation, loc_choice)
        loc_text_ctrl = wx.TextCtrl(self, wx.ID_ANY, size=(120,-1))
        loc_text_ctrl.Disable()

        # Note
        note_text      = wx.StaticText(self, wx.ID_ANY, "Note: ")
        note_text_ctrl = wx.TextCtrl(self, wx.ID_ANY, size=(160,-1))
        
        
        drug_sizer = wx.GridBagSizer(hgap=3, vgap=3)
        drug_sizer.Add(name_text,        pos=(0,1), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT)
        drug_sizer.Add(name_text_ctrl,   pos=(0,2), span=(1,2), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
        drug_sizer.Add(conc_text,        pos=(1,1), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT)
        drug_sizer.Add(conc_text_ctrl,   pos=(1,2), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
        drug_sizer.Add(conc_post_text,   pos=(1,3), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
        drug_sizer.Add(time_text,        pos=(2,1), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT)
        drug_sizer.Add(time_picker,      pos=(2,2), span=(1,2), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
        drug_sizer.Add(loc_text,         pos=(3,1), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT)
        drug_sizer.Add(loc_choice,       pos=(3,2), span=(1,2), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
        drug_sizer.Add(loc_text_ctrl,    pos=(4,1), span=(1,3), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT)
        drug_sizer.Add(note_text,        pos=(5,1), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT)
        drug_sizer.Add(note_text_ctrl,   pos=(5,2), span=(1,2), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
        drug_sizer.Add(wx.StaticText(self, wx.ID_ANY, " "), pos=(6,4))
        self.SetSizer(drug_sizer)

        # keep track of controls
        self.name_text_ctrl     = name_text_ctrl
        self.conc_text_ctrl     = conc_text_ctrl
        self.time_picker        = time_picker
        self.loc_choice         = loc_choice
        self.loc_text_ctrl      = loc_text_ctrl
        self.note_text_ctrl     = note_text_ctrl

    def __eq__(self, other):
        self_values = self.get_values()
        other_values = other.get_values()
        # invisible drugs are equal to everything (odd convention but works)
        if not self.visible or not other.visible:
            return True
        for sval, oval in zip(self_values, other_values):
            if sval != oval:
                return False
        return True
    def __ne__(self, other):
        return not self == other

    def OnCheck(self, event):
        self.logger.debug('Is checked = %s' % event.IsChecked())
        if event.IsChecked():
            self.make_visible()
        else:
            self.make_invisible()

    def OnLocation(self, event):
        self.logger.debug('Setting Location=%s' % event.GetString())
        self.loc_type = event.GetString()
        if ":" in self.loc_type:
            self.loc_text_ctrl.Enable()
        else:
            self.loc_text_ctrl.Disable()

    def make_invisible(self):
        self.logger.debug('Making invisible')
        self.SetTransparent(80)
        self.SetBackgroundColour(wx.Colour(210,190,190))
        self.visible=False
        self.cb.SetValue(False)
        self.parent.GetParent().calc_drugs()

    def make_visible(self):
        self.logger.debug('Making visible')
        self.SetTransparent(255)
        self.SetBackgroundColour(wx.Colour(190,210,190))
        self.visible=True
        self.cb.SetValue(True)
        self.parent.GetParent().calc_drugs()

    def set_values(self, name, concentration, time, location, note):
        '''
        Set all the drug values.  Use make_visible and make_invisible to set checkbox.
        '''
        self.logger.debug('Setting values')
        # name
        self.name_text_ctrl.SetValue(name)
        # concentration
        self.conc_text_ctrl.SetValue(str(concentration))
        # time
        ddt = wx.DateTime() # dummy date time for conversion
        ddt.SetJDN(time+0.00000001) # small number added, to fix rounding error
        self.time_picker.SetValue(ddt)
        # location
        if ':' in location:
            tokens = location.split(':')
            self.loc_choice.SetStringSelection(tokens[0]+':')
            self.loc_text_ctrl.SetValue(tokens[1])
            self.loc_text_ctrl.Enable()
        else:
            self.loc_choice.SetStringSelection(location)
            self.loc_text_ctrl.Disable()
        # note    
        self.note_text_ctrl.SetValue(note)

    def get_values(self):
        '''
        Returns all the values associated with an electrode.
        '''
        name          = self.name_text_ctrl.GetValue()
        concentration = self.conc_text_ctrl.GetValue()
        time          = self.time_picker.GetWxDateTime().GetJDN()
        location      = self.loc_choice.GetStringSelection()
        if ':' in location:
            location += self.loc_text_ctrl.GetValue()
        note       = self.note_text_ctrl.GetValue()
        return name, concentration, time, location, note


class Drug_panel(wx.Panel):
    '''
    This is the drug info panel.  It collects all the information about all drugs.
    '''
    def __init__(self, parent, id, *args, **kwargs):
        wx.Panel.__init__(self, parent, id, *args, **kwargs)
        self.logger = logging.getLogger('Run_experiment.Sess_panel.Drug_panel')
        self.parent = parent
        
        self.drug_panel_description = wx.StaticText(self, wx.ID_ANY, 'Pharmacology:    Drugs = %d     ' % (0) )
        drug_add_button = wx.Button(self, wx.ID_ANY, "Add Drug")
        self.Bind(wx.EVT_BUTTON, self.OnAddDrug, drug_add_button)
        drug_reorder_button = wx.Button(self, wx.ID_ANY, "Reorder List")
        self.Bind(wx.EVT_BUTTON, self.OnReorder, drug_reorder_button)
        self.sp = scrolled.ScrolledPanel(self, wx.ID_ANY, size=(400,207), style=wx.TAB_TRAVERSAL|wx.BORDER_SUNKEN)
        self.sp.SetupScrolling(scroll_y=False)
        # make an electrode in the elec_panel
        self.drugs = []

        # lay out the sp
        self.sp_gbs = wx.GridBagSizer(hgap=3, vgap=3)
        self.sp.SetSizer(self.sp_gbs)

        # elec_panel layout
        gbs = wx.GridBagSizer(hgap=3, vgap=3)
        gbs.Add(self.drug_panel_description, pos=(0,0), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
        gbs.Add(drug_reorder_button, pos=(0,1), flag=wx.ALIGN_RIGHT)
        gbs.Add(drug_add_button, pos=(0,2), flag=wx.ALIGN_RIGHT)
        gbs.Add(self.sp, pos=(1,0), span=(1,3))
        self.SetSizer(gbs)

    def __eq__(self, other):
        # make sure there are the same number of visible drugs
        self_visible_drugs = 0
        for sdrug in self.drugs:
            if sdrug.visible:
                self_visible_drugs += 1
        other_visible_drugs = 0
        for odrug in other.drugs:
            if odrug.visible:
                other_visible_drugs += 1
        if self_visible_drugs != other_visible_drugs:
            self.logger.info('different number of drugs found.')
            return False
        for sdrug in self.drugs:
            self_found_in_other = False
            for odrug in other.drugs:
                if sdrug == odrug:
                    self_found_in_other = True
            if not self_found_in_other:
                self.logger.info('new drug not found.')
                return False
        return True

    def __ne__(self, other):
        return not self == other

    def set_everything_from_database(self, dbcur, sess_id):
        dbcur.execute("""SELECT name, concentration, time_applied, location, note 
                         FROM pharmacology WHERE sess_id = ?""", (sess_id,))
        self.clear_drugs()
        for row in dbcur:
            self.add_drug(*row)

    def add_drug(self, name, concentration, time, location, note):
        '''
        Add a drug, and fill it with these values.
        '''
        self.logger.debug('Adding a drug with values supplied.')
        self.OnAddDrug( None )
        self.drugs[-1].set_values(name, concentration, time, location, note)
        self.calc_drugs()

    def OnAddDrug(self, event):
        self.logger.debug('Creating new drug.')
        self.drugs.append(Drug(self.sp, wx.ID_ANY, len(self.drugs)+1,
                               style=wx.BORDER_RAISED|wx.DOUBLE_BORDER))
        self.sp_gbs.Add(self.drugs[-1], pos=(0, len(self.drugs)-1) )
        self.sp_gbs.Layout()
        self.sp.SetupScrolling(scroll_y=False, scrollToTop=False)
        self.drugs[-1].make_visible()

    def clear_drugs(self):
        '''
        Clear all drugs from this panel.
        '''
        self.logger.debug('Clearing all drugs.')
        for i in xrange(len(self.drugs)):
            self.sp_gbs.Remove(self.drugs[i])
            self.drugs[i].Destroy()
            
        self.drugs = []
        self.reorder_drugs()
        self.calc_drugs()
        

    def is_correct_order(self, drugs):
        # if any visibles follow an invisible, return false, otherwise return true
        self.logger.debug('Checking drug order.')
        found_invisible = False
        for e in drugs:
            if e.visible and found_invisible:
                return False
            if not e.visible:
                found_invisible = True
        return True
        
    def OnReorder(self, event):
        self.logger.debug('Reorder button pressed.')
        self.reorder_drugs()

    def reorder_drugs(self):
        self.logger.debug('Reordering drugs.')
        while not self.is_correct_order(self.drugs):
            # find the last visible electrode who has an invisible electrode before it.
            lve = 0 # assume no visible electrodes
            pie = lve
            search_list = [len(self.drugs)-1-i for i in xrange(len(self.drugs))]
            #print "searching in " + str(search_list)
            for i in xrange(len(search_list)-1):
                #print "checking positions %d and %d" % (search_list[i], search_list[i+1])
                if self.drugs[search_list[i]].visible and \
                   not self.drugs[search_list[i+1]].visible:
                    lve = search_list[i] # if we find one, keep its index
                    pie = search_list[i+1]
                    break
            #print "found positions %d and %d" % (pie, lve)
            # swap pie and lve if they are not equal
            if pie != lve:
                #print "swaping positions %d and %d" % (pie, lve)
                self.sp_gbs.SetItemPosition(self.drugs[lve],   (0,len(self.drugs)+2) )
                self.sp_gbs.SetItemPosition(self.drugs[pie],   (0,lve) )
                self.sp_gbs.SetItemPosition(self.drugs[lve],   (0,pie) )
                # swap their positions in the list as well
                swap = self.drugs[pie]
                self.drugs[pie]   = self.drugs[lve]
                self.drugs[lve]   = swap
        self.sp_gbs.Layout()
        self.sp.SetupScrolling(scroll_y=False, scrollToTop=False)


    def calc_drugs(self):
        # determine how many drug panels are visible.
        self.logger.debug('Count up drugs.')
        visible = 0
        for d in self.drugs:
            if d.visible:
                visible += 1

        # update the description string.
        self.drug_panel_description.SetLabel('Pharmacology:    Drugs = %d     ' % (visible) )

    def get_values(self):
        '''
        Get the values for all drugs which are set to visible.
        '''
        self.logger.debug('Fetching values from all drugs.')
        names          = []
        concentrations = []
        times          = []
        locations      = []
        notes          = []
        
        # get all the electrodes information
        for drug in self.drugs:
            if drug.visible:
                na, co, ti, lo, no = drug.get_values()
                names.append(na)
                concentrations.append(co)
                times.append(ti)
                locations.append(lo)
                notes.append(no)

        return names, concentrations, times, locations, notes

# ===========================================================================================================
#                                                 Eye_panel
# ===========================================================================================================
class Eye_panel(wx.Panel):
    '''
    This is the eye_cup info panel.  It collects all the information about the eye_cup.
    '''
    def __init__(self, parent, id, *args, **kwargs):
        wx.Panel.__init__(self, parent, id, *args, **kwargs)
        self.logger = logging.getLogger('Run_experiment.Sess_panel.Eye_panel')
        self.parent = parent
        
        eye_description = wx.StaticText(self, wx.ID_ANY, "Eye Cup")
    
        # image of eye cup
        eye_image = wx.Image(os.path.join('setup','sample_eye_cup.jpg')).Scale(160,120, quality=wx.IMAGE_QUALITY_HIGH).ConvertToBitmap()
        eye_view  = wx.StaticBitmap(self, wx.ID_ANY, eye_image, size=(eye_image.GetWidth(),eye_image.GetHeight()),
                                    style=wx.BORDER_SIMPLE)
        
        # info panel
        info_panel = wx.Panel(self, wx.ID_ANY)
        bw_check   = wx.CheckBox(info_panel, wx.ID_ANY, label="Reverse Colors")
        str_text   = wx.StaticText(info_panel, wx.ID_ANY, 'Streak:')
        cup_text   = wx.StaticText(info_panel, wx.ID_ANY, 'Cup:')
        nerve_text = wx.StaticText(info_panel, wx.ID_ANY, 'Nerve:')
        # set button
        eye_set_button = wx.Button(info_panel, wx.ID_ANY, "Set")
        self.Bind(wx.EVT_BUTTON, self.OnSet, eye_set_button)

        # set up the sizers
        info_sizer = wx.GridBagSizer(hgap=5, vgap=5)
        info_sizer.Add(bw_check,   pos=(1,1), flag=wx.ALIGN_LEFT)
        info_sizer.Add(str_text,   pos=(2,1), flag=wx.ALIGN_LEFT)
        info_sizer.Add(cup_text,   pos=(3,1), flag=wx.ALIGN_LEFT)
        info_sizer.Add(nerve_text, pos=(4,1), flag=wx.ALIGN_LEFT)
        info_sizer.Add(eye_set_button, pos=(6,1), flag=wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL)
        info_sizer.Add(wx.StaticText(self, wx.ID_ANY, " "), pos=(7,2))
        info_panel.SetSizer(info_sizer)
        
        eye_sizer = wx.GridBagSizer(hgap=5, vgap=5)
        eye_sizer.Add(eye_description,  pos=(0,1), span=(1,2), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER_HORIZONTAL)
        eye_sizer.Add(eye_view,         pos=(1,1), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER_HORIZONTAL)
        eye_sizer.Add(info_panel,       pos=(1,2), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER_HORIZONTAL)
        self.SetSizer(eye_sizer)

        # save some variables we might care about later
        self.eye_view   = eye_view
        self.bw_check   = bw_check
        self.str_text   = str_text
        self.cup_text   = cup_text
        self.nerve_text = nerve_text
        self.sizer = eye_sizer
        self.info_sizer = info_sizer

        # set up the eye_cup
        from   stimuli.helper_codes.Eye_cup import Eye_cup
        self.eye_cup = Eye_cup.from_eye_cup_file(filename=os.path.join('setup','sample_eye_cup.txt'))
        #print self.eye_cup.cup

    def get_values_from_database(self, eyec_id):
        # find the eye_coordinates entry with the eye_id
        self.logger.debug('Getting eye_cup information from the database with eyec_id=%d', eyec_id)
        dbcur.execute(
            """SELECT streak_x,      streak_y,      streak_ang, 
                      cup_x,         cup_y,         cup_diameter, 
                      nerve_x,       nerve_y,       nerve_diameter 
               FROM eye_cup WHERE eyec_id=?""", (eyec_id,))
        ev = dbcur.fetchone()
        if ev != None:
            # prepare eye_coordinates to be used in UI
            streak      = [ev[0], ev[1], ev[2]]
            cup         = [ev[3], ev[4], ev[5]]
            nerve       = [ev[6], ev[7], ev[8]]
            eye_cup_image_location = '/experiment_data/stored_data/eye_cup_images/%d/eye_cup.jpg' % eyec_id
            self.logger.debug('Getting eye_cup image: %s' % eye_cup_image_location)
            eye_image = wx.Image(eye_cup_image_location).Scale(
                                  160,120, quality=wx.IMAGE_QUALITY_HIGH).ConvertToBitmap() 

            # return an Eye_cup object that we can pass to the stimuli
            from stimuli.helper_codes.Eye_cup import Eye_cup
            self.logger.debug('Setting up Eye_cup object with info from the database.')
            eye_cup = Eye_cup(streak, cup, nerve)
            # write these values to the eye_cup.txt file
            # eye_cup.write_eye_cup_file()
            return eye_cup, eye_image
        else:
            raise RuntimeError( "eyec_id=%d does not exist in the database" % eyec_id )

    def submit_to_database(self):       
        # find out if any entries in the database are identical to the entry we want to put in.
        dbcur.execute(
            """SELECT eyec_id FROM eye_cup WHERE streak_x=?      AND streak_y=?      AND streak_ang=? 
                                                AND cup_x=?         AND cup_y=?         AND cup_diameter=? 
                                                AND nerve_x=?       AND nerve_y=?       AND nerve_diameter=?""",
                           (int(self.eye_cup.streak[0]), int(self.eye_cup.streak[1]), self.eye_cup.streak[2], 
                            int(self.eye_cup.cup[0]),    int(self.eye_cup.cup[1]),    int(self.eye_cup.cup[2]), 
                            int(self.eye_cup.nerve[0]),  int(self.eye_cup.nerve[1]),  int(self.eye_cup.nerve[2])) )
        rows = dbcur.fetchall()
        if len(rows) > 0:
            # since an identical entry was found, don't put another entry into the database.
            self.logger.debug("Entry already exists, with eyec_id = %d" % (rows[0][0]))
        else:
            # since no identical entries were found, insert this new entry.
            self.logger.debug("Putting values into database.")
            dbcur.execute(
             """INSERT INTO eye_cup (streak_x,      streak_y,      streak_ang, 
                                        cup_x,         cup_y,         cup_diameter, 
                                        nerve_x,       nerve_y,       nerve_diameter) 
                VALUES                 (?,?,?,?,?,?,?,?,?)""", 
                           (int(self.eye_cup.streak[0]), int(self.eye_cup.streak[1]), self.eye_cup.streak[2], 
                            int(self.eye_cup.cup[0]),    int(self.eye_cup.cup[1]),    int(self.eye_cup.cup[2]), 
                            int(self.eye_cup.nerve[0]),  int(self.eye_cup.nerve[1]),  int(self.eye_cup.nerve[2])) )
            # find the eyec_id we just added
            dbcur.execute("SELECT MAX(eyec_id) FROM eye_cup")
            eyec_id, = dbcur.fetchone()

            # put the eye_cup_image into perminant storage.
            # since this can fail if the directory already exists...
            try:
                os.system('mkdir -p /experiment_data/stored_data/eye_cup_images/%d' % eyec_id)
            except:
                pass
            try:
                # move eye_cup.jpg to storage directory (overwrite if neccessary)
                os.system('cp -f eye_cup.jpg /experiment_data/stored_data/eye_cup_images/%d' % eyec_id)
            except:
                pass

    def set_everything(self, eye_cup, eye_image):
        # update the UI
        self.logger.debug('Setting all values')
        self.str_text.SetLabel(     'Streak: (%d, %d)    %d degrees' %\
            (eye_cup.streak[0], eye_cup.streak[1], eye_cup.streak[2]))
        self.cup_text.SetLabel(      'Cup: (%d, %d)    diameter %d' %\
            (eye_cup.cup[0], eye_cup.cup[1], eye_cup.cup[2]))
        self.nerve_text.SetLabel(    'Nerve: (%d, %d)    diameter %d' %\
            (eye_cup.nerve[0], eye_cup.nerve[1], eye_cup.nerve[2]))
        self.eye_view.SetBitmap(eye_image)
        self.info_sizer.Layout()
        self.sizer.Layout()
        self.parent.sizer.Layout()
        self.eye_cup = eye_cup
        self.eye_image = eye_image
        
    def get_values(self):
        self.logger.debug('Fetching values')
        return self.eye_cup, self.eye_image

    def panel_is_altered(self):
        # find out what the current session id is so we can find the associated eyec_id
        current_sess_id = self.parent.current_id
        dbcur.execute("SELECT eyec_id FROM session WHERE sess_id==?", (current_sess_id,))
        eyec_id, = dbcur.fetchone()
        if eyec_id is not None:
            db_eye_cup, db_eye_image = self.get_values_from_database(eyec_id)
        if self.eye_cup != db_eye_cup:
            return True
        return False

    def OnSet(self, event):
        shade = 1.0
        if self.bw_check.IsChecked():
            shade *=-1.0
        self.logger.debug('Setting eye coordinates with game controler.')
        import stimuli.helper_codes.set_eye_cup as sec
        sec.set_eye_cup(win_size,shade=shade, eye_cup=self.eye_cup)
        self.eye_cup.write_eye_cup_file()
        eye_image = wx.Image('eye_cup.jpg').Scale(160,120).ConvertToBitmap()
        self.set_everything(self.eye_cup, eye_image)

# ===========================================================================================================
#                                                 Settings_grid
# ===========================================================================================================
class Settings_grid(gridlib.Grid):
    def __init__(self, parent):
        gridlib.Grid.__init__(self, parent, wx.ID_ANY)
        self.logger = logging.getLogger('Run_experiment.Settings_grid')
        self.parent = parent
        
        # create the grid no rows and 3 columns
        self.CreateGrid(0, 3)

        # ---- column settings ----
        # set the first column to be right algned and to be read only.
        attr1 = gridlib.GridCellAttr()
        attr1.SetReadOnly(True)
        attr1.SetAlignment(wx.ALIGN_RIGHT, wx.ALIGN_CENTER)
        self.SetColAttr(0, attr1)
        self.SetColLabelValue(0, 'Setting Name')
        # set the second column to be float numbers which can be edited.
        attr2 = gridlib.GridCellAttr()
        attr2.SetEditor(gridlib.GridCellFloatEditor())
        attr2.SetAlignment(wx.ALIGN_CENTER, wx.ALIGN_CENTER)
        self.SetColAttr(1, attr2)
        self.SetColLabelValue(1, 'Value')
        # set the third column to be left aligned and read only
        attr3 = gridlib.GridCellAttr()
        attr3.SetReadOnly(True)
        attr3.SetAlignment(wx.ALIGN_LEFT, wx.ALIGN_CENTER)
        self.SetColAttr(2, attr3)
        self.SetColLabelValue(2, 'Description')

        self.SetColSize(0,110)
        self.SetColSize(1,90)
        self.SetColSize(2,650)

        self.values = []

    def set_values(self, value_set):
        '''
        This sets all the values in the settings_grid.
        Inputs:
            value_set      : A list of values which have the form
                              [name, description, value].  These values will be
                              inserted into the grid into columns [0,2,1] respectively.
        '''
        if type(value_set[0]) == list:
            # call this function on every element in the list instead
            for vs in value_set:
                self.set_values(vs)
        else:
            v = value_set # for readability
            self.values.append(v) # remember the values we've entered
            row = len(self.values) - 1 # insert new value into this row
            self.InsertRows(row)
            if '[list]' in v[1]:
                self.SetCellEditor(row, 1, gridlib.GridCellTextEditor())
                if not isinstance(v[2], list): # if it is not
                    v[2] = [v[2]] # make it one

            self.SetCellValue(row, 0, str(v[0]))
            self.SetCellValue(row, 1, str(v[2]))
            self.SetCellValue(row, 2, str(v[1]))

    def clear_values(self):
        '''
        This sets all the values in the settings_grid to blank.
        '''
        self.logger.debug('Clearing values')
        if len(self.values) != 0:
            self.DeleteRows(numRows=len(self.values))
            self.values = []

    def get_values(self):
        '''
        Return the values associated with each setting.
        Inputs:
        Returns:
            values       : a list of values ready to be given as
                            arguements to the stimulus function.
        '''
        self.logger.debug('Fetching settings values')
        return_values = []
        for i in xrange(len(self.values)):
            return_values.append( eval(self.GetCellValue(i, 1)) )
        return return_values

    def get_everything(self, validated=False):
        '''
        Return the (name, value, description) associated with each setting.
        '''
        self.logger.debug('Fetching everything')
        names        = []
        values       = []
        descriptions = []
        for i in xrange(len(self.values)):
            name        = self.GetCellValue(i,0)
            value       = eval(self.GetCellValue(i,1))
            description = self.GetCellValue(i,2)
            if '[list]' in description:
                if not isinstance(value, list): # if its not a list, make it one
                    value = [value] 
                    self.SetCellValue(i,1, str(value)) # update the grid too.
            names.append(        name  )
            values.append(       value )
            descriptions.append( description ) 
        return names, values, descriptions
        

class Search_panel(wx.Panel):
    '''
    This panel allows the user to search for responses to visual stimuli.
    '''
    def __init__(self, parent, id, *args, **kwargs):
        wx.Panel.__init__(self, parent, id, *args, **kwargs)
        self.logger = logging.getLogger('Run_experiment.Sess_panel.Search_panel')
        self.parent = parent
        
        description = wx.StaticText(self, wx.ID_ANY,
                          'Search for responces with:  ')

        # find out what ways to search exist.
        files = os.listdir('stimuli')
        search_list = []
        for f in files:
            if f[-3:] == '.py' and 'search' in f:
                search_list.append(f[:-3])
        self.stim_choice = wx.Choice(self, wx.ID_ANY, choices=search_list)
        self.Bind(wx.EVT_CHOICE, self.OnChoice, self.stim_choice)

        # chalk button
        search_button = wx.Button(self, wx.ID_ANY, "Run Stimulus")
        self.Bind(wx.EVT_BUTTON, self.OnSearch, search_button)

        # add the stimulus settings grid
        self.grid_scroller = scrolled.ScrolledPanel(self, size=(860,125), style=wx.BORDER_SUNKEN)
        self.settings_grid = Settings_grid(self.grid_scroller)
        self.settings_grid.SetRowLabelSize(0)

        # lay out the sp
        self.sp_gbs = wx.GridBagSizer(hgap=3, vgap=3)
        self.sp_gbs.Add(self.settings_grid, pos=(0,0))
        self.grid_scroller.SetSizer(self.sp_gbs)

        # set up sizer
        sizer = wx.GridBagSizer(hgap=5, vgap=5)
        
        # add items to sizers
        # -- main page sizer
        sizer.Add(description,        pos=(0,0),
                                        flag=wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
        sizer.Add(self.stim_choice,  pos=(0,1)) 
        sizer.Add(search_button,       pos=(0,2),
                                        flag=wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL)
        sizer.Add(self.grid_scroller, pos=(1,0), span=(1,3),
                                        flag=wx.ALIGN_CENTER_HORIZONTAL)
        self.SetSizer(sizer)

        # have the first available stimulus selected for settings grid input
        self.select_stim(search_list[0])
        

    def OnSearch(self, event):
        self.logger.debug('Run Stimulus pressed')
        self.run_search()

    def run_search(self, preview=True):
        self.logger.info("Running stimulus named %s with preview=%s" % (self.SelectedStim, preview))
        # get the current eye_cup information from the database
        eye_cup = self.parent.eye_panel.eye_cup
        # set the eye_cup up to be drawn
        eye_cup.preped_for_drawing=False # let the program set it up
        # get information from the setting_grid
        values = tuple(self.settings_grid.get_values())
        # import the code to run the stimulus
        exec( "import stimuli.%s as %s" % (self.SelectedStim, self.SelectedStim) )
        self.logger.debug( "import stimuli.%s as %s" % (self.SelectedStim, self.SelectedStim))
        kwargs = {'preview':preview, 'eye_cup':eye_cup}
        exec( "times = %s(*values, **kwargs)" % self.SelectedStim )

    def OnChoice(self, event):
        self.logger.debug('Search type=%s selected.' % event.GetString())
        self.select_stim(event.GetString())

    def select_stim(self, stim_type):
        # FIXME this should take into acount the subversion repository revision of stim_type
        self.logger.debug('Preparing settings_grid for stim_type=%s' % stim_type)
        self.SelectedStim = stim_type
        self.stim_choice.SetStringSelection(self.SelectedStim)
        # retrieve the settings for this stimulus
        exec("from stimuli.%s import %s_settings" % (self.SelectedStim, self.SelectedStim))
        settings = eval('%s_settings()' % (self.SelectedStim))
        # set the default settings in the self.settings_grid
        self.settings_grid.clear_values()
        for s in settings:
            if type(s) == type(True):
                self.SelectedStimIsTriggerable = s
            else:
                # this setting is normal, so put its values in the grid normally
                self.settings_grid.set_values(s)

        self.sp_gbs.Layout()
        self.grid_scroller.SetupScrolling(scroll_x=False, scrollToTop=False)
        self.settings_grid.SetGridCursor(0,1)
        
# ===========================================================================================================
#                                                 Sess_panel
# ===========================================================================================================
class Sess_panel(wx.Panel):
    '''
    This is the session panel, it collects information about one brain "configuration".
    '''
    def __init__(self, parent, id, *args, **kwargs):
        wx.Panel.__init__(self, parent, id, *args, **kwargs)
        self.logger = logging.getLogger('Run_experiment.Sess_panel')
        self.parent = parent
        self.current_id = 0
        
    def clear_everything(self):
        """
        Clear everything in this panel so we can start fresh.
        """
        if self.GetSizer() is not None:
            self.GetSizer().DeleteWindows()
        
    def setup_ui(self):
        """
        Setup the ui elements, but don't put any values in any fields.
        """
        self.logger.debug("Setting up the ui elements for this panel.")
        description = wx.StaticText(self, wx.ID_ANY,
                          'A Session consists of one "brain configuration".')
        elec_panel = Elec_panel(self, wx.ID_ANY)
        drug_panel = Drug_panel(self, wx.ID_ANY)
        search_panel = Search_panel(self, wx.ID_ANY)
        eye_panel  = Eye_panel(self, wx.ID_ANY, style=wx.SUNKEN_BORDER)
         

        # notes
        note_text = wx.StaticText(self, wx.ID_ANY, "Session Notes:")
        note_text_ctrl = wx.TextCtrl(self, wx.ID_ANY, size=(450,150),
                                     style=wx.TE_MULTILINE )

        # submit panel
        subm_panel = wx.Panel(self, wx.ID_ANY)
        # submit button
        submit_button = wx.Button(subm_panel, wx.ID_ANY, "Submit")
        self.Bind(wx.EVT_BUTTON, self.OnSubmit, submit_button)
        # prev button
        prev_button = wx.Button(subm_panel, wx.ID_ANY, "<", size=(30,-1))
        self.Bind(wx.EVT_BUTTON, self.OnPrev, prev_button)
        # next button
        next_button = wx.Button(subm_panel, wx.ID_ANY, ">", size=(30,-1))
        self.Bind(wx.EVT_BUTTON, self.OnNext, next_button)
        # store data button
        store_button = wx.Button(subm_panel, wx.ID_ANY, "Store Data")
        self.Bind(wx.EVT_BUTTON, self.OnStore, store_button)
        subm_sizer = wx.GridBagSizer(hgap=3,vgap=3)
        subm_sizer.Add(prev_button, pos=(0,0))
        subm_sizer.Add(submit_button, pos=(0,1))
        subm_sizer.Add(next_button, pos=(0,2))
        subm_sizer.Add(store_button, pos=(0,3))
        subm_sizer.Add(wx.StaticText(self, wx.ID_ANY, ""), pos=(1,4))
        subm_panel.SetSizer(subm_sizer)

        # Set up the sizers
        sess_sizer = wx.GridBagSizer(hgap=5, vgap=5)
        
        # add items to sizers
        # -- main page sizer
        sess_sizer.Add(description, pos=(0,1), span=(1,2),
                                     flag=wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL)
        sess_sizer.Add(search_panel, pos=(2,1), span=(1,2))
        sess_sizer.Add(elec_panel,  pos=(1,1)) 
        sess_sizer.Add(drug_panel,  pos=(1,2))
        sess_sizer.Add(note_text,   pos=(3,1), flag=wx.ALIGN_LEFT|wx.ALIGN_BOTTOM)
        sess_sizer.Add(eye_panel,   pos=(3,2), span=(3,1))
        sess_sizer.Add(note_text_ctrl, pos=(4,1), flag=wx.ALIGN_TOP|wx.ALIGN_CENTER_HORIZONTAL)
        
        sess_sizer.Add(subm_panel,  pos=(5,1) )
        self.SetSizer(sess_sizer)
        sess_sizer.Layout() # needed to update the screen after a self.clear_everything
        self.sizer = sess_sizer

        # keep track of things who's values we care about later.
        self.elec_panel     = elec_panel
        self.drug_panel     = drug_panel
        self.eye_panel      = eye_panel
        self.note_text_ctrl = note_text_ctrl
        self.next_button    = next_button
        self.prev_button    = prev_button
        self.search_panel   = search_panel


    def panel_is_altered(self):
        if self.current_id == 0:
            return True
        # if the eye_cup has been altered then this panel has...
        if self.eye_panel.panel_is_altered():
            self.logger.info('Eye cup values are not reflected in database.')
            return True
        # check the electrode panel
        db_elec_panel = Elec_panel(self, wx.ID_ANY)
        db_elec_panel.set_everything_from_database(dbcur, self.current_id)
        db_elec_panel.Show(False)
        if self.elec_panel != db_elec_panel:
            self.logger.info('The electrode(s) information is not reflected in the database.')
            return True
        # check the drug panel
        db_drug_panel = Drug_panel(self, wx.ID_ANY)
        db_drug_panel.set_everything_from_database(dbcur, self.current_id)
        db_drug_panel.Show(False)
        if self.drug_panel != db_drug_panel:
            self.logger.info('The pharmacology information is not reflected in the database.')
            return True
        # NOTE
        dbcur.execute("SELECT note FROM session WHERE sess_id = ?", (self.current_id,))
        sess_note, = dbcur.fetchone()
        tsess_note = self.note_text_ctrl.GetValue()
        if sess_note != tsess_note:
            self.logger.info('The session note is not reflected in the database.')
            return True
        return False

    def reset_everything_to_database(self):
        """
        Restore everything on this page back to the way it was based on the current_ids
        """
        self.set_everything_from_database(self.current_id)

    def set_everything_from_database(self, id):
        """
        Set everything on this page from the database with id supplied.
            If id == 0 then clear all entries.
            if id == -1 then make id the max id in the database
        """
        sess_id = id
        self.clear_everything()
        self.setup_ui()
        current_spec_id = self.parent.spec_panel.current_id
        if sess_id == 0:
            self.logger.debug("Setting up session panel with sess_id=0 (clean).")
            self.current_id = 0
        elif sess_id == -1:
            # find the database entry with highest sess_id given this spec_id
            dbcur.execute( "SELECT MAX(sess_id) FROM session WHERE spec_id=?", (current_spec_id,) )
            sess_id, = dbcur.fetchone()
            if sess_id is None: # no session in database to set up
                self.logger.debug( "No sessions for this specimin entry: spec_id=%d" 
                                   % current_spec_id )
                self.current_id = 0
            else: # set up this session from the database
                self.current_id = sess_id
                self.set_everything( self.current_id )
                self.logger.debug('Set up session panel with sess_id=%d found in the db' % self.current_id)
        else:
            # just set up the page with values corresponding to the spec_id passed.
            self.current_id = sess_id
            self.set_everything( self.current_id )
            self.logger.debug('Set up session panel with sess_id=%d passed' % self.current_id)
             
        self.parent.page[2]['panel_is_setup'] = False
        self.parent.page[3]['panel_is_setup'] = False
        self.manage_prev_next_state()

    def set_everything(self, sess_id):
        '''
        Should not be run directly, this is just a helper function for self.set_everything_from_database()
        Knowing the session id number, set all the values for all the electrodes, drugs, and eye_cup information.
        '''
        self.logger.debug("setting everything with sess_id = %d" % sess_id)

        # ELECTRODES
        self.elec_panel.set_everything_from_database(dbcur, sess_id)

        # DRUGS
        self.drug_panel.set_everything_from_database(dbcur, sess_id)

        # EYE CUP
        # find the eyec_id for this session, then set the values and update the ui.
        dbcur.execute("SELECT eyec_id FROM session WHERE sess_id=?", (sess_id,))
        eyec_id, = dbcur.fetchone()
        eye_cup, eye_image = self.eye_panel.get_values_from_database(eyec_id)
        self.eye_panel.set_everything(eye_cup, eye_image)

        # NOTE
        dbcur.execute("SELECT note FROM session WHERE sess_id = ?", (sess_id,))
        sess_note, = dbcur.fetchone()
        self.note_text_ctrl.SetValue( sess_note )


    def OnPrev(self, event):
        ''' if possible, set all values in this panel to previous entry in database '''
        # set all values
        self.logger.debug("Hit Previous button")
        # show dialog if anything has changed vs. database
        if self.panel_is_altered():
            dlg = Altered_panel_confirmation_dialog(self, wx.ID_ANY)
            dlg.ShowModal()
            if self.action_choice == 'Continue':
                self.set_everything_from_database(self.prev_sess_id)
                self.logger.info("Updated session panel to database sess_id=%d" % (self.prev_sess_id))
            else:
                return # do nothing if user canceled action
        else: # no changes detected
            self.set_everything_from_database(self.prev_sess_id)
            self.logger.info("Updated session panel to database sess_id=%d" % (self.prev_sess_id))

    def OnNext(self, event):
        ''' If possible, set all values in this panel to NEXT entry in database. '''
        self.logger.debug("Hit Next button")
        # show dialog if anything has changed vs. database
        if self.panel_is_altered():
            dlg = Altered_panel_confirmation_dialog(self, wx.ID_ANY)
            dlg.ShowModal()
            if self.action_choice == 'Continue':
                self.set_everything_from_database(self.next_sess_id)
                self.logger.info("Updated session panel to database sess_id=%d" % (self.next_sess_id))
            else:
                return # do nothing if user canceled action
        else: # no changes detected
            self.set_everything_from_database(self.next_sess_id)
            self.logger.info("Updated session panel to database sess_id=%d" % (self.next_sess_id))
            
    def OnStore(self, event):
        # FIXME I changed the dialog to help fix problem with file times being out of sync, need new dialog
        #   for this now
        self.logger.debug("Looking for data to store.")
        dlg = Find_data_dialog(self, wx.ID_ANY, 0, 0, style=wx.STAY_ON_TOP)
        dlg.ShowModal()
        pass

    def archive_data(self, filename):
        # FIXME add database entry for this.
        # this is different than the expe_panel version because it doesn't know anything about experiments table
        if filename != None:
            self.logger.debug('archiving data')
            # find out what we should rename the data file to.
            new_name = '1_%s-' % (self.search_panel.SelectedStim)
            grid_names, grid_values, grid_descriptions = self.search_panel.settings_grid.get_everything()
            # format lists and such so they comply with filename restrictions
            for n,v in zip(grid_names,grid_values):
                strv = ""
                if type(v) == type([]):
                    for val in v:
                        strv += "%3.1f, " % val
                    # remove the last comma and space
                    strv = strv[:-2]
                else:
                    strv = "%3.1f" % v

                if n == "refresh_rate" or n == "win_size":
                    pass # don't save win size or refresh rate... filenames are too long
                else:
                    new_name += '-%s %s' % (n,strv)
            if len(new_name) > 256:
                new_name = new_name[:256]
            if debug_save_mode:
                t = wx.DateTime.Today()
                debug_folder = '/experiment_data/debug/%d-%d-%d/search_mode' % (t.GetMonth()+1, t.GetDay(), t.GetYear())
                os.system('mkdir -p %s' % debug_folder )
                os.system('cp -f eye_cup.jpg "%s/eye_cup.jpg"' % debug_folder)
                os.system('cp -f eye_cup.txt "%s/eye_cup.txt"' % debug_folder)
                # See if data with this filename already exists, if so we'll append a number to it and try again.
                exists = True
                new_id = 2
                while exists:
                    filenames = os.listdir(debug_folder)
                    found_match = False
                    for fn in filenames:
                        if new_name in fn:
                            found_match = True
                    if found_match:
                        new_name = ('%d_'% new_id) + new_name[2:]
                        new_id = 3
                    else:
                        exists = False
                os.system('cp -f "/experiment_data/upload_data/%s" "%s/%s"' % (filename, debug_folder, new_name))
            new_folder = '/experiment_data/stored_data/specimin_%d/' %\
                      (self.parent.spec_panel.current_id)
            os.system('mkdir -p %s' % new_folder )
            os.system('mv -f "/experiment_data/upload_data/%s" "%s/%s"' % (filename, new_folder, new_name))
        else:
            self.logger.debug('no data to archive')


    def manage_prev_next_state(self):
        ''' determine the enable/disabled state of previous and next buttons. '''
        self.logger.debug('Managing the previous/next buttons state.')
        # determine the all the sess_ids in the database for the current spec_id
        dbcur.execute( "SELECT sess_id FROM session WHERE spec_id=?", 
                       (self.parent.spec_panel.current_id,) )
        rows = dbcur.fetchall()
        if len(rows) > 0:
            self.sess_ids = [rows[i][0] for i in xrange(len(rows))]
            self.sess_ids.sort()
        else:
            self.sess_ids = []

        # find the next highest and next lowest sess_id from the current_sess_id
        self.prev_sess_id = 0
        self.next_sess_id = 0
        #print self.current_id
        #print self.sess_ids
        for s in self.sess_ids:
            if s < self.current_id:
                self.prev_sess_id = s
            if s > self.current_id:
                self.next_sess_id = s
                break
        
        # potentially disable next button
        if self.next_sess_id == 0:
            self.next_button.Disable()
        else:
            self.next_button.Enable()

        # potentially disable prev button
        if self.prev_sess_id == 0:
            self.prev_button.Disable()
        else:
            self.prev_button.Enable()

    def OnSubmit(self, event):
        # submit a session entry
        self.logger.debug('Submitting values to the database.')
        # find the specimin id for the current specimin.
        spec_id = self.parent.spec_panel.current_id
        note    = self.note_text_ctrl.GetValue()
        dbcur.execute("INSERT INTO session (spec_id, note) VALUES (?,?)", (spec_id, note))

        # find the session id for the current session
        dbcur.execute("SELECT MAX(sess_id) FROM session")
        sess_id = dbcur.fetchone()[0]
        self.logger.debug("new sess_id=%d for spec_id=%d " % (sess_id,spec_id))
        self.current_id = sess_id

        # ELECTRODES
        evs = self.elec_panel.get_values()
        #print evs
        # put the information into the database.
        for i in xrange(len(evs[0])):
            dbcur.execute(
                """INSERT INTO electrode (sess_id, type, trodes, resistance, location, depth, note) 
                   VALUES                (?,?,?,?,?,?,?)""",
                (sess_id, evs[0][i], evs[1][i], evs[2][i], evs[3][i], evs[4][i], evs[5][i]))
            
        # DRUGS
        dvs = self.drug_panel.get_values()
        # put the information into the database.
        for i in xrange(len(dvs[0])):
            dbcur.execute(
                """INSERT INTO pharmacology (sess_id, name, concentration, time_applied, location, note) 
                   VALUES                   (?,?,?,?,?,?)""",
                (sess_id, dvs[0][i], dvs[1][i], dvs[2][i], dvs[3][i], dvs[4][i]))

        # EYE_COORDS
        self.eye_panel.submit_to_database()

        # FINISH UP SESSION ENTRY
        # find the last submitted eyec_id
        dbcur.execute("SELECT MAX(eyec_id) FROM eye_cup")
        eyec_id, = dbcur.fetchone()
        # insert this id into the last submitted session entry
        dbcur.execute("UPDATE session SET eyec_id=? WHERE sess_id=?", (eyec_id,sess_id))
        # find out what revision the subversion repository is at.
        svnlog = os.popen("svn info")
        lines  = svnlog.readlines()
        for l in lines:
            if 'Last Changed Rev:' in l:
                tokens = l.split(':') # first split on 'r' then split on | to pull out the revision number
                svnrev = int(tokens[1])
                self.logger.debug('subversion repository is at revision: %d' % svnrev)
        dbcur.execute("UPDATE session SET svn_revision=? WHERE sess_id=?", (svnrev, sess_id))
        self.logger.info('Submitted entry to database, specimin_id=%d, session_id=%d' % (spec_id,sess_id))

        # make changes to database perminant
        if not debug_mode:
            dbcon.commit()
            self.logger.info('Committed all changes to the database %s/%s.' % (database_path, database_name) )

        self.parent.page[2]['panel_is_setup'] = False
        self.parent.page[3]['panel_is_setup'] = False
        self.manage_prev_next_state()

# ===========================================================================================================
#                                                 RF_panel
# ===========================================================================================================
class RF_panel(wx.Panel):
    '''
    This is the RF info panel.  It collects all the information about the RF.
    '''
    def __init__(self, parent, id, *args, **kwargs):
        wx.Panel.__init__(self, parent, id, *args, **kwargs)
        self.logger = logging.getLogger('Run_experiment.Chalk_panel.RF_panel')
        self.parent = parent
        
        description = wx.StaticText(self, wx.ID_ANY, "Receptive Field")
    
        # image of eye cup
        rf_image = wx.Image(os.path.join('setup','sample_rf.jpg')).Scale(160,120, quality=wx.IMAGE_QUALITY_HIGH).ConvertToBitmap()
        rf_view  = wx.StaticBitmap(self, wx.ID_ANY, rf_image, size=(rf_image.GetWidth(),rf_image.GetHeight()),
                                    style=wx.BORDER_SIMPLE)
        
        # info panel
        info_panel = wx.Panel(self, wx.ID_ANY)
        bw_check         = wx.CheckBox(info_panel, wx.ID_ANY, label="Reverse Colors")
        center_text      = wx.StaticText(info_panel, wx.ID_ANY, 'Center:')
        size_text        = wx.StaticText(info_panel, wx.ID_ANY, 'Size:')
        orientation_text = wx.StaticText(info_panel, wx.ID_ANY, 'Orientation:')
        # set button
        set_button = wx.Button(info_panel, wx.ID_ANY, "Set")
        self.Bind(wx.EVT_BUTTON, self.OnSet, set_button)

        # set up the sizers
        info_sizer = wx.GridBagSizer(hgap=5, vgap=5)
        info_sizer.Add(bw_check,         pos=(0,1), flag=wx.ALIGN_LEFT)
        info_sizer.Add(center_text,      pos=(1,1), flag=wx.ALIGN_LEFT)
        info_sizer.Add(size_text,        pos=(2,1), flag=wx.ALIGN_LEFT)
        info_sizer.Add(orientation_text, pos=(3,1), flag=wx.ALIGN_LEFT)
        info_sizer.Add(set_button,       pos=(4,1), flag=wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL)
        info_sizer.Add(wx.StaticText(self, wx.ID_ANY, " "), pos=(5,2)) # for to make pretty
        info_panel.SetSizer(info_sizer)
        
        rf_sizer = wx.GridBagSizer(hgap=5, vgap=5)
        rf_sizer.Add(description,      pos=(0,1), span=(1,2), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER_HORIZONTAL)
        rf_sizer.Add(rf_view,          pos=(1,1), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER_HORIZONTAL)
        rf_sizer.Add(info_panel,       pos=(1,2), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER_HORIZONTAL)
        self.SetSizer(rf_sizer)

        # save some variables we might care about later
        self.rf_view          = rf_view
        self.bw_check         = bw_check
        self.center_text      = center_text
        self.size_text        = size_text
        self.orientation_text = orientation_text
        self.sizer      = rf_sizer
        self.info_sizer = info_sizer

        # set up the eye_cup
        from   stimuli.helper_codes.RF import RF
        self.rf = RF.from_rf_file(filename=os.path.join('setup','sample_rf.txt'))
        

    def get_values_from_database(self, rf_id):
        # find the receptive_field entry with the rf_id
        self.logger.debug('Getting receptive field information from the database with rf_id=%d', rf_id)
        dbcur.execute(
            """SELECT center_x,      center_y, 
                      size_x,        size_y, 
                      ori 
               FROM rf WHERE rf_id=?""", (rf_id,))
        rfv = dbcur.fetchone()
        if rfv != None:
            # prepare receptive_field info to be used in UI
            center      = [rfv[0], rfv[1]]
            size        = [rfv[2], rfv[3]]
            ori         =  rfv[4]
            rf_image_location = '/experiment_data/stored_data/receptive_field_images/%d/rf.jpg' % rf_id
            self.logger.debug('Getting rf image: %s' % rf_image_location)
            rf_image = wx.Image(rf_image_location).Scale(
                                  160,120, quality=wx.IMAGE_QUALITY_HIGH).ConvertToBitmap() 

            # return an RF object that we can pass to the stimuli
            from stimuli.helper_codes.RF import RF
            self.logger.debug('Setting up RF object with info from the database.')
            rf = RF(center, size, ori)
            # write these values to the rf.txt file
            rf.write_rf_file()
            return rf, rf_image
        else:
            raise RuntimeError( "rf_id=%d does not exist in the database" % rf_id )

    def submit_to_database(self):       
        # find out if any entries in the database are identical to the entry we want to put in.
        dbcur.execute(
            """SELECT rf_id FROM rf WHERE center_x=?  AND center_y=?
                                      AND size_x=?    AND size_y=?
                                      AND ori=? """,
                           (int(self.rf.center[0]), int(self.rf.center[1]), 
                            int(self.rf.size[0]),    int(self.rf.size[1]), 
                            int(self.rf.ori) ) )
        rows = dbcur.fetchall()
        if len(rows) > 0:
            # since an identical entry was found, don't put another entry into the database.
            rf_id = rows[0][0]
            self.logger.debug("Entry already exists, with rf_id = %d" % (rf_id))
        else:
            # since no identical entries were found, insert this new entry.
            self.logger.debug("Putting values into database.")
            dbcur.execute(
             """INSERT INTO rf (center_x,      center_y, 
                                size_x,        size_y, 
                                ori) 
                VALUES         (?,?,?,?,?) """, 
                           (int(self.rf.center[0]), int(self.rf.center[1]), 
                            int(self.rf.size[0]),    int(self.rf.size[1]), 
                            int(self.rf.ori) ) )
            # find the rf_id we just added
            dbcur.execute("SELECT MAX(rf_id) FROM rf")
            rf_id, = dbcur.fetchone()

            # put the rf_image into perminant storage.
            try:
                os.system('mkdir -p /experiment_data/stored_data/receptive_field_images/%d' % rf_id)
            except:
                pass
            try:
                # move eye_cup.jpg to storage directory (overwrite if neccessary)
                os.system('cp -f rf.jpg /experiment_data/stored_data/receptive_field_images/%d/' % rf_id)
            except:
                pass

        return rf_id

    def set_everything(self, rf, rf_image):
        # update the UI
        self.logger.debug('Setting all values')
        self.center_text.SetLabel(     'Center: (%d, %d)' %
            (rf.center[0], rf.center[1]))
        self.size_text.SetLabel(      'Size: (%d, %d)' %
            (rf.size[0], rf.size[1]))
        self.orientation_text.SetLabel(    'Orientation: %d degrees' % rf.ori)
        self.rf_view.SetBitmap(rf_image)
        self.info_sizer.Layout()
        self.sizer.Layout()
        self.parent.sizer.Layout()
        self.rf = rf
        self.rf_image = rf_image
        
    def get_values(self):
        self.logger.debug('Fetching values')
        return self.rf, self.rf_image

    def OnSet(self, event):
        shade = 1.0
        if self.bw_check.IsChecked():
            shade *= -1.0
        self.logger.debug('Setting rf with mouse.')
        import stimuli.helper_codes.set_rf as srf
        self.rf = srf.set_rf(win_size,shade=shade, eye_cup=self.parent.parent.sess_panel.eye_panel.eye_cup, 
                   rf=self.rf)
        self.rf.write_rf_file()
        rf_image = wx.Image('rf.jpg').Scale(160,120).ConvertToBitmap()
        self.set_everything(self.rf, rf_image)
# ===========================================================================================================
#                                                 Chalk_images_panel
# ===========================================================================================================

class Chalk_images_panel(wx.Panel):
    '''
    This is the chalk images panel.  It collects all the information about all times you tried to chalk 
        out the rf.
    '''
    def __init__(self, parent, id, *args, **kwargs):
        wx.Panel.__init__(self, parent, id, *args, **kwargs)
        self.logger = logging.getLogger('Run_experiment.Chalk_panel.Chalk_images_panel')
        
        description = wx.StaticText(self, wx.ID_ANY, 'Chalking Images:') 

        self.sp = scrolled.ScrolledPanel(self, wx.ID_ANY, size=(400,140), style=wx.BORDER_SUNKEN)
        self.sp.SetupScrolling(scroll_y=False)
        self.images = []

        # lay out the sp
        self.sp_gbs = wx.GridBagSizer(hgap=3, vgap=3)
        self.sp.SetSizer(self.sp_gbs)

        gbs = wx.GridBagSizer(hgap=3, vgap=3)
        gbs.Add(description, pos=(0,0), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
        gbs.Add(self.sp, pos=(1,0), span=(1,3))
        self.SetSizer(gbs)

    def setup(self, path='.'):
        """
        Set up this panel with images we find laying around.
        """
        # find all the images in this directory with 'chalk' in their name.
        self.clear_images()
        files = os.listdir(path)
        for file in files:
            if 'chalk' in file and '.jpg' == file[-4:]:
                self.logger.debug("Setting up image: %s" % file)
                image = wx.Image(file).Scale(160,120).ConvertToBitmap()
                self.add_image(image)
        

    def add_image(self, image):
        '''
        Add an image.
        '''
        self.logger.debug('Adding an image.')
        view  = wx.StaticBitmap(self.sp, wx.ID_ANY, image, size=(image.GetWidth(),image.GetHeight()),
                                    style=wx.BORDER_SIMPLE)
        self.images.append(view)
        self.sp_gbs.Add(self.images[-1], pos=(0, len(self.images)-1) )
        self.sp_gbs.Layout()
        self.sp.SetupScrolling(scroll_y=False, scrollToTop=False)

    def clear_images(self):
        '''
        Clear all images from this panel.
        '''
        self.logger.debug('Clearing all images.')
        for i in xrange(len(self.images)):
            self.sp_gbs.Remove(self.images[i])
            self.images[i].Destroy()
            
        self.images = []
        
# ===========================================================================================================
#                                                 Chalk_panel
# ===========================================================================================================
class Chalk_panel(wx.Panel):
    '''
    This is the Chalk panel, used to chalk out the receptive field.
    '''
    def __init__(self, parent, id, *args, **kwargs):
        wx.Panel.__init__(self, parent, id, *args, **kwargs)
        self.logger = logging.getLogger('Run_experiment.Chalk_panel')
        self.parent = parent
        self.current_id = 0 # this is the rf_id for the rf table
        
    def clear_everything(self):
        """
        Clear everything in this panel so we can start fresh.
        """
        if self.GetSizer() is not None:
            self.GetSizer().DeleteWindows()
        
    def setup_ui(self):
        """
        Setup the ui elements, but don't put any values in any fields.
        """
        description = wx.StaticText(self, wx.ID_ANY,
                          "Chalk out the receptive field.")

        # stimulus selector dropdown menu
        stims = self.find_stimuli()
        stim_choice   = wx.Choice(self, wx.ID_ANY, choices=stims)
        self.Bind(wx.EVT_CHOICE, self.OnStim, stim_choice)

        # chalk button
        chalk_button = wx.Button(self, wx.ID_ANY, "Chalk with these settings.")
        self.Bind(wx.EVT_BUTTON, self.OnChalk, chalk_button)

        # add the stimulus settings grid
        self.grid_scroller = scrolled.ScrolledPanel(self, size=(860,185), style=wx.BORDER_SUNKEN)
        self.settings_grid = Settings_grid(self.grid_scroller)
        self.settings_grid.SetRowLabelSize(0)

        # lay out the sp
        self.sp_gbs = wx.GridBagSizer(hgap=3, vgap=3)
        self.sp_gbs.Add(self.settings_grid, pos=(0,0))
        self.grid_scroller.SetSizer(self.sp_gbs)

        # chalk images panel
        self.chalk_images_panel = Chalk_images_panel(self, wx.ID_ANY)

        # rf_panel
        self.rf_panel = RF_panel(self, wx.ID_ANY)

        # receptive field notes
        note_text = wx.StaticText(self, wx.ID_ANY,
                          "Receptive field notes:")
        note_text_ctrl = wx.TextCtrl(self, wx.ID_ANY, size=(860,80),
                                     style=wx.TE_MULTILINE )

        # submit panel
        subm_panel = wx.Panel(self, wx.ID_ANY)
        # submit button
        submit_button = wx.Button(subm_panel, wx.ID_ANY, "Submit")
        self.Bind(wx.EVT_BUTTON, self.OnSubmit, submit_button)
        # prev button
        prev_button = wx.Button(subm_panel, wx.ID_ANY, "<", size=(30,-1))
        self.Bind(wx.EVT_BUTTON, self.OnPrev, prev_button)
        # next button
        next_button = wx.Button(subm_panel, wx.ID_ANY, ">", size=(30,-1))
        self.Bind(wx.EVT_BUTTON, self.OnNext, next_button)
        subm_sizer = wx.GridBagSizer(hgap=3,vgap=3)
        subm_sizer.Add(prev_button, pos=(0,0))
        subm_sizer.Add(submit_button, pos=(0,1))
        subm_sizer.Add(next_button, pos=(0,2))
        subm_sizer.Add(wx.StaticText(self, wx.ID_ANY, ""), pos=(1,3))
        subm_panel.SetSizer(subm_sizer)

        # Set up the sizers
        sizer = wx.GridBagSizer(hgap=5, vgap=5)
        
        # add items to sizers
        # -- main page sizer
        sizer.Add(description,             pos=(1,1),
                       flag=wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL)
        sizer.Add(stim_choice,             pos=(1,2),
                       flag=wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
        sizer.Add(chalk_button,            pos=(1,3),
                       flag=wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
        sizer.Add(self.grid_scroller,      pos=(2,1), span=(1,3) )
        sizer.Add(self.chalk_images_panel, pos=(3,1), span=(1,2))
        sizer.Add(note_text,               pos=(4,1), span=(1,3))
        sizer.Add(note_text_ctrl,          pos=(5,1), span=(1,3))
        sizer.Add(self.rf_panel,           pos=(3,3))
        sizer.Add(subm_panel,              pos=(6,1))
        self.SetSizer(sizer)
        self.sizer = sizer
        self.sizer.Layout()

        self.next_button    = next_button
        self.prev_button    = prev_button
        self.note_text_ctrl = note_text_ctrl
        self.stim_choice    = stim_choice

        # have the first available stimulus selected for settings grid input
        self.select_stim(stims[0])

    def panel_is_altered(self):
        if self.current_id == 0:
            return True
        # rf coordinates
        db_rf, db_rf_image = self.rf_panel.get_values_from_database(self.current_id)
        rf, rf_image = self.rf_panel.get_values()
        if rf != db_rf:
            self.logger.info("Receptive field not reflected in database.")
            return True
        # rf note
        dbcur.execute("SELECT note FROM rf WHERE rf_id==?", (self.current_id,))
        db_note, = dbcur.fetchone()
        note = self.note_text_ctrl.GetValue()
        if db_note is None:
            db_note = ''
        if note != db_note:
            self.logger.info("rf note not reflected in database.")
            return True
        return False

    def reset_everything_to_database(self):
        """
        Restore everything on this page back to the way it was based on the current_ids
        """
        self.set_everything_from_database(self.current_id)


    def set_everything_from_database(self, id):
        """
        Set everything on this page from the database with id supplied.
            If id == 0 then clear all entries.
            if id == -1 then make id the max id in the database
        """
        rf_id = id
        self.clear_everything()
        self.setup_ui()
        current_sess_id = self.parent.page[1]['panel'].current_id
        if rf_id == 0:
            self.logger.debug("Setting up chalk panel with rf_id=0 (clean).")
            self.current_id = 0
        elif rf_id == -1:
            # find the highest rf_id in the db for the current_sess_id
            dbcur.execute("SELECT MAX(rf_id) FROM rf WHERE sess_id=?",
                          (current_sess_id,) )
            rf_id, = dbcur.fetchone()
            if rf_id is None:
                self.logger.info(
                    "No receptive fields have been set up for this session, sess_id=%d"
                    % current_sess_id )
                self.current_id = 0
            else:
                self.current_id = rf_id
                self.set_everything( self.current_id )
                self.logger.debug('Set up chalk panel with rf_id=%d found in the DB.'
                                  % self.current_id )
        else:
            # just set up the page with the values corresponding to the rf_id passed.
            self.current_id = rf_id
            self.set_everything( self.current_id )
            self.logger.debug('Set up chalk panel with rf_id=%d passed'
                              % self.current_id )

        self.parent.page[3]['panel_is_setup'] = False
        self.manage_prev_next_state()
        self.sizer.Layout()
    
    def set_everything(self, rf_id):
        '''
        Should not be run directly, this is just a helper function for self.set_everything_from_database()
        Knowing the rf id number, set all the values for all the receptive field and load images.
        '''
        self.logger.debug("setting everything with rf_id = %d" % rf_id)
        # set the rf panel
        self.rf_panel.set_everything(*self.rf_panel.get_values_from_database(rf_id))
        # first clear all chalk images in the run directory.
        files = os.listdir('.')
        for file in files:
            if 'chalk' in file and file.endswith('.jpg'):
                os.system('rm -f "%(file)s"' % locals())
        # copy chalk images from perminant storage to cwd
        psd = '/experiment_data/stored_data/receptive_field_images/%d' % rf_id
        files = os.listdir( psd )
        for file in files:
            if 'chalk' in file and '.jpg' == file[-4:]:
                os.system( 'cp -f "%s/%s" "./%s"' % (psd, file, file) )
        # run setup on chalk_images_panel
        self.chalk_images_panel.setup()
        
        dbcur.execute("SELECT note FROM rf WHERE rf_id==?", (rf_id,))
        note, = dbcur.fetchone()
        # set the note.
        if note is not None:
            self.note_text_ctrl.SetValue(note)
        else:
            self.note_text_ctrl.SetValue('')

    def OnSubmit(self, event):
        note = self.note_text_ctrl.GetValue()
        if note == '':
            note = None # submit None to database if note is empty
        
        # submit rf entry to the database.
        self.current_id = self.rf_panel.submit_to_database()
        self.logger.debug("Created receptive field rf_id=%d in the database."
                          % self.current_id)
        # update the RF entry with note
        dbcur.execute("UPDATE rf SET note=?,sess_id=? WHERE rf_id==?",
                      (note, self.parent.sess_panel.current_id, self.current_id) )
        # copy 'chalk' images to perminant storage folder.
        self.logger.debug("Moving chalk images to /experiment_data/stored_data/receptive_field_images/%d" % 
                           self.current_id )
        files = os.listdir('.')
        for file in files:
            if 'chalk' in file and '.jpg' == file[-4:]:
                # only submit if the timestamp on the chalk image comes after the specimin.date
                UTC_time_stamp = os.stat(file).st_mtime 
                JDN_time_stamp = UTCtoJDN(UTC_time_stamp)
                # get the time from the specimin panel
                specimin_time = self.parent.spec_panel.date_picker.GetValue().GetJDN()
                if specimin_time > JDN_time_stamp:
                    file_ddt = wx.DateTime()
                    spec_ddt = wx.DateTime()
                    file_ddt.SetJDN(JDN_time_stamp)
                    spec_ddt.SetJDN(specimin_time)
                    file_time = file_ddt.Format("%Y/%m/%d %H:%M:%S")
                    spec_time = spec_ddt.Format("%Y/%m/%d %H:%M:%S")
                    self.logger.debug("File: %s was created before this specimin, so it is not being archived." % file)
                    self.logger.debug("  --File: %(file_time)s Specimin: %(spec_time)s" % locals())
                else:
                    self.logger.debug("Archiving %s." % file)
                    os.system('cp -f "%s" /experiment_data/stored_data/receptive_field_images/%d/' %
                          (file, self.current_id))

        # make changes to database perminant
        if not debug_mode:
            dbcon.commit()
            self.logger.info('Committed all changes to the database %s/%s.' % (database_path, database_name) )

        self.parent.page[3]['panel_is_setup'] = False
        self.manage_prev_next_state()
        

    def OnChalk(self, event):
        self.logger.debug("Chalk button pressed")
        # run the stimulus with preview=False
        self.run_stimulus()
        self.chalk_images_panel.setup()

    def run_stimulus(self, preview=False):
        self.logger.info("Running stimulus named %s with preview=%s" % (self.SelectedStim, preview))
        # get the current eye_cup information from the database
        sess_id = self.parent.sess_panel.current_id
        dbcur.execute('SELECT eyec_id FROM session WHERE sess_id=?',
                      (sess_id,) )
        try:
            eyec_id, = dbcur.fetchone()
        except:
            raise RuntimeError("No eyecup information, you must run 'set' on the session panel and submit changes.")
        self.logger.debug( "found that current eyec_id should be: %d" % eyec_id )
        eye_cup, eye_image = self.parent.sess_panel.eye_panel.get_values_from_database(eyec_id)

        # get information from the setting_grid
        values = tuple(self.settings_grid.get_values())
        #print values
        # import the code to run the stimulus
        exec( "import stimuli.%s as %s" % (self.SelectedStim, self.SelectedStim) )
        self.logger.debug( "import stimuli.%s as %s" % (self.SelectedStim, self.SelectedStim))
        kwargs = {'preview':preview, 'eye_cup':eye_cup}
        exec( "times = %s(*values, **kwargs)" % self.SelectedStim )
        
        
    def OnPrev(self, event):
        ''' if possible, set all values in this panel to previous entry in database '''
        # set all values
        self.logger.debug("Hit Previous button")
        # show dialog if anything has changed vs. database
        if self.panel_is_altered():
            dlg = Altered_panel_confirmation_dialog(self, wx.ID_ANY)
            dlg.ShowModal()
            if self.action_choice == 'Continue':
                self.set_everything_from_database(self.prev_rf_id)
                self.logger.info("Updated rf panel to database rf_id=%d" % (self.prev_rf_id))
            else:
                return # do nothing if user canceled action
        else: # no changes detected
            self.set_everything_from_database(self.prev_rf_id)
            self.logger.info("Updated rf panel to database rf_id=%d" % (self.prev_rf_id))
             

    def OnNext(self, event):
        ''' If possible, set all values in this panel to NEXT entry in database. '''
        self.logger.debug("Hit Next button")
        # show dialog if anything has changed vs. database
        if self.panel_is_altered():
            dlg = Altered_panel_confirmation_dialog(self, wx.ID_ANY)
            dlg.ShowModal()
            if self.action_choice == 'Continue':
                self.set_everything_from_database(self.next_rf_id)
                self.logger.info("Updated rf panel to database rf_id=%d" % (self.next_rf_id))
            else:
                return # do nothing if user canceled action
        else: # no changes detected
            self.set_everything_from_database(self.next_rf_id)
            self.logger.info("Updated rf panel to database rf_id=%d" % (self.next_rf_id))


    def manage_prev_next_state(self):
        ''' determine the enable/disabled state of previous and next buttons. '''
        self.logger.debug('Managing previous/next buttons state')
        # find all the rf_ids in the database
        current_sess_id = self.parent.page[1]['panel'].current_id
        dbcur.execute( "SELECT rf_id FROM rf WHERE sess_id=?", (current_sess_id,) ) 
        rows = dbcur.fetchall()
        if len(rows) > 0:
            self.rf_ids = [rows[i][0] for i in xrange(len(rows))]
            self.rf_ids.sort()
        else:
            self.rf_ids = []

        # find the next highest and next lowest rf_id from the current_rf_id
        self.prev_rf_id = 0
        self.next_rf_id = 0
        for r in self.rf_ids:
            if r < self.current_id:
                self.prev_rf_id = r
            if r > self.current_id:
                self.next_rf_id = r
                break
        
        # potentially disable next button
        if self.next_rf_id == 0:
            self.next_button.Disable()
        else:
            self.next_button.Enable()

        # potentially disable prev button
        if self.prev_rf_id == 0:
            self.prev_button.Disable()
        else:
            self.prev_button.Enable()
        
    def OnStim(self, event):
        self.logger.debug('Stimulus type=%s selected.' % event.GetString())
        self.select_stim(event.GetString())

    def select_stim(self, stim_type):
        # FIXME this should take into acount the subversion repository revision of stim_type
        self.logger.debug('Preparing settings_grid for stim_type=%s' % stim_type)
        self.SelectedStim = stim_type
        self.stim_choice.SetStringSelection(self.SelectedStim)
        # retrieve the settings for this stimulus
        exec("from stimuli.%s import %s_settings" % (self.SelectedStim, self.SelectedStim))
        settings = eval('%s_settings()' % (self.SelectedStim))
        # set the default settings in the self.settings_grid
        self.settings_grid.clear_values()
        for s in settings:
            if type(s) == type(True):
                self.SelectedStimIsTriggerable = s
            else:
                # this setting is normal, so put its values in the grid normally
                self.settings_grid.set_values(s)

        self.sp_gbs.Layout()
        self.grid_scroller.SetupScrolling(scroll_x=False, scrollToTop=False)
        self.settings_grid.SetGridCursor(0,1)
    
    def find_stimuli(self):
        '''
        This function tells you what stimuli are available to be shown and their
            respective settings.
        '''
        self.logger.debug('Finding out what kinds of stimuli are available.')
        # find all the stimulus defining files.
        stim_names = []
        files = os.listdir('stimuli')
        for file in files:
            if '.py' == file[-3:] and "chalk" in file:
                stim_names.append(file[:-3]) # cut off the _stim.py
        return stim_names


# ===========================================================================================================
#                                                 Altered_panel_confirmation_dialog
# ===========================================================================================================
class Altered_panel_confirmation_dialog(wx.Dialog):
    '''
    This is a dialog that opens so as to alert the user that something on this panel is not in line with the
        current database entry and if they continue their action those changes will be lost.
    '''
    def __init__(self, parent, id, *args, **kwargs):
        wx.Dialog.__init__(self, parent, id, *args, **kwargs)

        self.logger = logging.getLogger('Run_experiment.Altered_panel_confirmation_dialog')
        self.parent = parent

        layout_panel = wx.Panel(self, wx.ID_ANY)
        description_text = "Something on this page was altered since the last time you pressed submit.\n"
        description_text += "(look at the command line info for further information)\n\n"
        description_text += "Click continue to discard those changes, or cancel to go back and submit them."
        description = wx.StaticText(layout_panel, wx.ID_ANY, description_text )

        ok_button = wx.Button(layout_panel, wx.ID_ANY, "Continue (Discard any Changes)")
        self.Bind(wx.EVT_BUTTON, self.OnOK, ok_button)
        self.ok_button = ok_button
        cancel_button = wx.Button(layout_panel, wx.ID_ANY, "Cancel")
        self.Bind(wx.EVT_BUTTON, self.OnCancel, cancel_button)

        # Set up the sizers
        sizer = wx.GridBagSizer(hgap=5, vgap=5)

        sizer.Add(description,      pos=(1,1), span=(1,2),
                                     flag=wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL)
        sizer.Add(ok_button,        pos=(2,1))
        sizer.Add(cancel_button,    pos=(2,2))
        sizer.Add(wx.StaticText(self,wx.ID_ANY,''), pos=(3,3)) # to make things look pretty
        layout_panel.SetSizer(sizer)
        self.layout_sizer = sizer

        # Set up the sizers
        sizer = wx.GridBagSizer(hgap=5, vgap=5)
        sizer.Add(layout_panel,    pos=(0,0))
        self.sizer = sizer
        self.SetSizerAndFit(sizer)

    def OnOK(self, event):
        self.logger.debug('User clicked Continue (Discard any Changes).')
        self.parent.action_choice = 'Continue'
        self.Close()
        self.Destroy()

    def OnCancel(self, event):
        self.logger.debug('User clicked Cancel.')
        self.parent.action_choice = 'Cancel'
        self.Close()
        self.Destroy()

# ===========================================================================================================
#                                                 New_or_old_dialog
# ===========================================================================================================
class New_or_old_dialog(wx.Dialog):
    '''
    This is a dialog that opens as the program is launched to ask the user to
        start a new database entry or use the latest database entries to fill panels.
    '''
    def __init__(self, parent, id, *args, **kwargs):
        wx.Dialog.__init__(self, parent, id, *args, **kwargs)

        self.logger = logging.getLogger('Run_experiment.New_or_old_dialog')
        self.parent = parent

        # find out how long since the last database entry (last experiment)
        dbcur.execute('SELECT time, MAX(expe_id) FROM experiment')
        last_expe_time, last_expe_id = dbcur.fetchone()
        if last_expe_time is None:
            last_expe_time = 0.0
            last_expe_id = 0
        now_time = wx.DateTime().Now().GetJDN() # dummy date time for conversions
        days_since_last_expe    = now_time - last_expe_time
        hours_since_last_expe   = days_since_last_expe * 24
        minutes_since_last_expe = hours_since_last_expe * 60
        self.logger.debug( 'Found the latest experiment (expe_id=%d) was run %d minutes ago.'% (last_expe_id, minutes_since_last_expe) )

        layout_panel = wx.Panel(self, wx.ID_ANY)
        description_text = "Would you like to start from scratch or use previous database values?\n"
        description_text += "Since it has been %d DAYS  %d Hours  and  %d minutes since the last experiment was run, \n" % (
                             days_since_last_expe, hours_since_last_expe % 24, minutes_since_last_expe % 60 )
        if minutes_since_last_expe > 100: # probably want to start from scratch instead
            description_text += "it is recommended that you start from scratch."
        else:
            description_text += "you may want to use the previous database values."
        description = wx.StaticText(layout_panel, wx.ID_ANY, description_text )

        ok_button = wx.Button(layout_panel, wx.ID_ANY, "Start from scratch")
        self.Bind(wx.EVT_BUTTON, self.OnOK, ok_button)
        self.ok_button = ok_button
        cancel_button = wx.Button(layout_panel, wx.ID_ANY, "Use previous database values")
        self.Bind(wx.EVT_BUTTON, self.OnCancel, cancel_button)
        close_button = wx.Button(layout_panel, wx.ID_ANY, "Cancel (don't open program)")
        self.Bind(wx.EVT_BUTTON, self.OnClose, close_button)

        # Set up the sizers
        sizer = wx.GridBagSizer(hgap=5, vgap=5)

        sizer.Add(description,      pos=(1,1), span=(1,3),
                                     flag=wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL)
        sizer.Add(ok_button,        pos=(2,1))
        sizer.Add(cancel_button,    pos=(2,2))
        sizer.Add(close_button,    pos=(2,3))
        sizer.Add(wx.StaticText(self,wx.ID_ANY,''), pos=(3,4)) # to make things look pretty
        layout_panel.SetSizer(sizer)
        self.layout_sizer = sizer

        # Set up the sizers
        sizer = wx.GridBagSizer(hgap=5, vgap=5)
        sizer.Add(layout_panel,    pos=(0,0))
        self.sizer = sizer
        self.SetSizerAndFit(sizer)

    def OnOK(self, event):
        self.logger.debug('Start from scratch selected.')
        self.parent.initial_spec_id = 0
        self.parent.user_canceled = False
        self.Close()
        self.Destroy()

    def OnCancel(self, event):
        self.logger.debug('Use previous database values.')
        self.parent.initial_spec_id = -1
        self.parent.user_canceled = False
        self.Close()
        self.Destroy()

    def OnClose(self, event):
        self.logger.debug('Cancel (do not open program)')
        self.parent.user_canceled = True
        self.parent.Close()
        self.parent.parent.Close()
        self.Close()
        self.Destroy()

# ===========================================================================================================
#                                                 Find_data_dialog
# ===========================================================================================================
class Find_data_dialog(wx.Dialog):
    '''
    This is a dialog that opens after an experiment is run telling the user 
        that data is being looked for and allowing them to cancel if they wish.
    FIXME
    while debugging, it will ask the user to confirm the renaming of the file.
    '''
    def __init__(self, parent, id, expe_num, expe_time, *args, **kwargs):
        wx.Dialog.__init__(self, parent, id, *args, **kwargs)
        self.logger = logging.getLogger('Run_experiment.Expe_panel.Find_data_dialog')
        self.parent = parent
        self.expe_time = expe_time
        self.count = 0

        description = wx.StaticText(self, wx.ID_ANY,
                         'Looking for data in "/experiment_data/upload_data".')

        looking_gauge = wx.Gauge(self, wx.ID_ANY, size=(250,25) )

        # initially empty, will fill this out later
        found_data_text = wx.StaticText(self, wx.ID_ANY, '')
        if expe_num:
            expe_text       = wx.StaticText(self, wx.ID_ANY, 
                               'This will be saved as expe_id:%(expe_num)d' % locals())
        else:
            expe_text       = wx.StaticText(self, wx.ID_ANY , '')
            
        expe_text.Hide()

        self.looking_gauge = looking_gauge
        self.found_data_text = found_data_text
        self.expe_text = expe_text

        ok_button = wx.Button(self, wx.ID_ANY, "OK", size=(200,-1))
        self.Bind(wx.EVT_BUTTON, self.OnOK, ok_button)
        self.ok_button = ok_button
        self.ok_button.Disable()
        cancel_button = wx.Button(self, wx.ID_ANY, "Cancel", size=(100,-1))
        self.Bind(wx.EVT_BUTTON, self.OnCancel, cancel_button)


        button_sizer = wx.BoxSizer(wx.HORIZONTAL)
        button_sizer.Add(ok_button,     0, flag=wx.ALIGN_CENTER_VERTICAL|
                                         wx.ALIGN_RIGHT|wx.RIGHT, border=7)
        button_sizer.Add(cancel_button, 0, flag=wx.ALIGN_CENTER_VERTICAL|
                                               wx.ALIGN_LEFT)

        # Set up the sizers
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(description,     0, flag=wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 
                  border=7)
        sizer.Add(looking_gauge,   0, flag=wx.ALIGN_CENTER_VERTICAL|
                                           wx.ALIGN_CENTER_HORIZONTAL)
        sizer.Add(found_data_text, 0, flag=wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 
                  border=7)
        sizer.Add(expe_text,       0, flag=wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 
                  border=7)
        sizer.Add(button_sizer,    0, flag=wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 
                  border=7)

        self.SetSizer(sizer)
        sizer.Fit(self)
        self.sizer = sizer
        self.upload_dir = '/experiment_data/upload_data'

        # let this dialog look for files
        self.Bind(wx.EVT_IDLE, self.OnIdle)
    
    def OnIdle(self, event):
        found_data = False
        files = os.listdir(self.upload_dir)

        # find all the files that have completed transfer over the network
        data_files = []
        for file in files:
            if '.rsv' not in file:
                data_files.append(file)

        if len(data_files) == 0:
            event.RequestMore()
            self.count += 1
            if self.count % 1500 == 1: # slow down the pulsing to a reasonable pace
                self.looking_gauge.Pulse()
            return
        elif len(data_files) > 1:
            event.RequestMore()
            self.found_data_text.SetLabel('Found more than one data file.\n You must correct this issue before proceeding.')
            self.sizer.Fit(self)
            self.sizer.Layout()
            return
        # if it got this far, we found the data.
        self.data_filename = data_files[0]

        # make sure the data file we found is timestamped AFTER the expe_time
        UTC_file_time = os.stat(os.path.join(self.upload_dir,self.data_filename)).st_mtime
        file_time = UTCtoJDN(UTC_file_time)
        if file_time < self.expe_time: 
            wx_file_time = wx.DateTime()
            wx_expe_time = wx.DateTime()
            wx_file_time.SetJDN(file_time)
            wx_expe_time.SetJDN(self.expe_time)
            self.logger.info('The data found in "/experiment/upload_data" was modified BEFORE the experiment was run.')
            self.found_data_text.SetLabel("The data file was created at %s which is BEFORE the \n stimulus concluded at %s" % (wx_file_time.Format("%T"),wx_expe_time.Format("%T")))
        else:
            self.logger.debug('Found Data.')
            self.found_data_text.SetLabel("Data has been found...")
        self.expe_text.Show()
    
        self.sizer.Fit(self)
        self.sizer.Layout()
        self.ok_button.Enable()
        self.Unbind(wx.EVT_IDLE)

    def OnOK(self, event):
        self.logger.debug('Pressed OK')
        if self.expe_time: # don't submit anything if run from store data button. FIXME
            self.parent.submit_experiment_to_database(self.expe_time)
        self.parent.archive_data(self.data_filename)
        self.Close()
        self.Destroy()

    def OnCancel(self, event):
        self.logger.debug('User pressed Cancel')
        self.Close()
        self.Destroy()

# ===========================================================================================================
#                                                 Expe_panel
# ===========================================================================================================
class Expe_panel(wx.Panel):
    '''
    This is the experiment panel, specify the type of experiment and its settings.
    '''
    def __init__(self, parent, id, *args, **kwargs):
        wx.Panel.__init__(self, parent, id, *args, **kwargs)
        self.logger = logging.getLogger('Run_experiment.Expe_panel')
        self.parent = parent
        self.current_id = 0


    def clear_everything(self):
        """
        Clear everything in this panel so we can start fresh.
        """
        if self.GetSizer() is not None:
            self.GetSizer().DeleteWindows()
            self.logger.debug("Clearing everything.")

    def calc_deg_to_rf(self, deg):
        '''
        Calculate the rf units for a given degrees of visual angle desired.
        '''
        # open up the database.id file to get the magnification of the optics and the size of the screen
        config_filename = '/experiment_data/stored_data/database.id'
        inv_magnification = from_config("inv_magnification", config_filename)
        monitor_width     = from_config("monitor_width", config_filename)
        
        # find out the dimensions of the RF
        rf = self.parent.chalk_panel.rf_panel.rf
        
        rf_width = rf.size[0] # this is in pixels
        rf_width = rf_width * monitor_width/1440 # and now this is in mm
        
        projected_rf_width = rf_width/inv_magnification # this is in mm on retina
        conversion_factor = 0.011/0.13 # from Kogo-Ariel 2001 paper on turtle retina
        projected_rf_deg = projected_rf_width / conversion_factor
        return deg/projected_rf_deg
        
    def setup_ui(self):
        """
        Setup the ui elements, but don't put any values in any fields.
        """
        self.logger.debug("Setting up ui elements.")

        # === stimulus select panel ====
        stim_panel = wx.Panel(self, wx.ID_ANY)

        description = wx.StaticText(stim_panel, wx.ID_ANY,
                          "Select a type of stimuli and its settings.")
        # stimulus selector dropdown menu
        stims = self.find_stimuli()
        stim_choice   = wx.Choice(stim_panel, wx.ID_ANY, choices=stims)
        self.Bind(wx.EVT_CHOICE, self.OnStim, stim_choice)

        # display background and eye_cup/rf transparency selectors
        bg_color_text   = wx.StaticText(stim_panel, wx.ID_ANY, "Background: ")
        #self.bg_color_choice = wx.Choice(stim_panel, wx.ID_ANY, choices=['Black','Grey','White'])
        self.bg_color_slider = wx.Slider(stim_panel, wx.ID_ANY, -2, -2, 2, size=(100,-1), 
                                style=wx.SL_HORIZONTAL|wx.SL_LABELS )
        self.Bind(wx.EVT_SLIDER, self.OnBackground, self.bg_color_slider)
        trans_text = wx.StaticText(stim_panel, wx.ID_ANY, "Eye Cup Opacity")
        self.trans_slider = wx.Slider( stim_panel, wx.ID_ANY, size=(250,-1),
                                style=wx.SL_HORIZONTAL|wx.SL_LABELS )
        self.Bind(wx.EVT_SLIDER, self.SliderUpdate, self.trans_slider)

        # degree to RF unit converter
        self.deg_text = wx.StaticText(stim_panel, wx.ID_ANY, "Input a size in degrees to convert.")
        deg_button = wx.Button(stim_panel, wx.ID_ANY, "Convert")
        self.Bind(wx.EVT_BUTTON, self.OnConvert, deg_button)
        self.deg_text_ctrl = wx.TextCtrl(stim_panel, wx.ID_ANY, size=(50,-1))
        deg_text2 = wx.StaticText(stim_panel, wx.ID_ANY, "degrees to RF units.")

        # === stimulus select panel sizer setup ===
        self.ssp_sizer = wx.GridBagSizer()
        self.ssp_sizer.Add(description,          pos=(0,0), 
                                           flag=wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL)
        self.ssp_sizer.Add(bg_color_text,        pos=(0,2),
                                           flag=wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
        self.ssp_sizer.Add(self.bg_color_slider, pos=(0,3),
                                           flag=wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL)
        self.ssp_sizer.Add(self.deg_text,        pos=(0,4), span=(1,3),
                                           flag=wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL)
        self.ssp_sizer.Add(stim_choice,          pos=(1,0), span=(2,1),
                                           flag=wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL)
        self.ssp_sizer.Add(self.trans_slider,    pos=(1,2), span=(1,2),
                                           flag=wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL)
        self.ssp_sizer.Add(deg_button,           pos=(1,4),
                                           flag=wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
        self.ssp_sizer.Add(self.deg_text_ctrl,   pos=(1,5),
                                           flag=wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL)
        self.ssp_sizer.Add(deg_text2,           pos=(1,6),
                                           flag=wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL)
        self.ssp_sizer.Add(trans_text,           pos=(2,2), span=(1,2),
                                           flag=wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL)
        stim_panel.SetSizer(self.ssp_sizer)

        # add the stimulus settings grid
        self.grid_scroller = scrolled.ScrolledPanel(self, size=(860,365), style=wx.BORDER_SUNKEN)
        self.settings_grid = Settings_grid(self.grid_scroller)
        self.settings_grid.SetRowLabelSize(0)
        self.Bind(gridlib.EVT_GRID_CELL_CHANGE, self.OnChange, self.settings_grid)

        # lay out the sp
        self.sp_gbs = wx.GridBagSizer(hgap=3, vgap=3)
        self.sp_gbs.Add(self.settings_grid, pos=(0,0))
        self.grid_scroller.SetSizer(self.sp_gbs)

        # run experiment panel
        run_panel = wx.Panel(self, wx.ID_ANY)
        # submit button
        preview_button = wx.Button(run_panel, wx.ID_ANY, "Preview")
        self.Bind(wx.EVT_BUTTON, self.OnPreview, preview_button)
        # previous button
        previous_button = wx.Button(run_panel, wx.ID_ANY, "<", size=(30,-1))
        self.Bind(wx.EVT_BUTTON, self.OnPrevious, previous_button)
        # next button
        next_button = wx.Button(run_panel, wx.ID_ANY, ">", size=(30,-1))
        self.Bind(wx.EVT_BUTTON, self.OnNext, next_button)
        # run experiment button
        run_button = wx.Button(run_panel, wx.ID_ANY, "Run experiment with these settings. Runtime = unknown.")
        # view frame time info checkbox
        vft_check =  wx.CheckBox(run_panel, wx.ID_ANY, label="View Frame Duration Information.")

        self.Bind(wx.EVT_BUTTON, self.OnRun, run_button)
        note_check_box = wx.CheckBox(run_panel, wx.ID_ANY, label="Link note when experiment is run.")
        run_sizer = wx.GridBagSizer(hgap=3,vgap=3)
        run_sizer.Add(previous_button, pos=(0,0), span=(2,1))
        run_sizer.Add(preview_button,  pos=(0,1), span=(2,1))
        run_sizer.Add(next_button,     pos=(0,2), span=(2,1))
        run_sizer.Add(run_button,      pos=(0,3), span=(2,1))
        run_sizer.Add(vft_check,       pos=(0,4))
        run_sizer.Add(note_check_box,  pos=(1,4))
        run_sizer.Add(wx.StaticText(self, wx.ID_ANY, ""), pos=(2,5))
        run_panel.SetSizer(run_sizer)

        # experimental note or abnormality
        note_text = wx.StaticText(self, wx.ID_ANY,
                          "Experiment note or abnormality:")
        note_text_ctrl = wx.TextCtrl(self, wx.ID_ANY, size=(700,50),
                                     style=wx.TE_MULTILINE )
        note_submit_button = wx.Button(self, wx.ID_ANY, "Link this note now.")
        self.Bind(wx.EVT_BUTTON, self.OnSubmit, note_submit_button)
        

        # Set up the sizers
        expe_sizer = wx.GridBagSizer(hgap=5, vgap=5)
        
        # add items to sizers
        # -- main page sizer
        expe_sizer.Add(stim_panel,          pos=(1,1), span=(1,2),
                                           flag=wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL)
        expe_sizer.Add(self.grid_scroller,  pos=(2,1), span=(1,2) )
        expe_sizer.Add(run_panel,           pos=(3,1))
        expe_sizer.Add(note_text,           pos=(4,1), span=(1,2))
        expe_sizer.Add(note_text_ctrl,      pos=(5,1), span=(1,2))
        expe_sizer.Add(note_submit_button,  pos=(6,1))
        self.SetSizer(expe_sizer)
        self.sizer = expe_sizer

        self.next_button = next_button
        self.prev_button = previous_button
        self.run_button  = run_button
        self.run_sizer = run_sizer
        self.note_check_box = note_check_box
        self.note_text_ctrl = note_text_ctrl
        self.stim_choice    = stim_choice
        self.vft_check      = vft_check

        # have the first available stimulus selected for settings grid input
        self.select_stim(stims[0])

    def panel_is_altered(self):
        # FIXME fill this out
        return False

    def reset_everything_to_database(self):
        """
        Restore everything on this page back to the way it was based on the current_ids
        """
        self.set_everything_from_database(self.current_id)

    def set_everything_from_database(self, id):
        """
        Set everything on this page from the database with id supplied.
            If id == 0 then clear all entries.
            if id == -1 then make id the max id in the database
        """
        expe_id = id
        self.clear_everything()
        self.setup_ui()
        current_rf_id = self.parent.chalk_panel.current_id
        if current_rf_id == 0:
            self.logger.info("You must set the RF before running experiments.")
            self.current_id = 0
            return
        if expe_id == 0:
            self.logger.debug("Setting up experiment panel with expe_id=0 (clean).")
            self.current_id = 0
        elif expe_id == -1:
            # find the highest expe_id for the current_sess_id in the DB
            dbcur.execute("SELECT MAX(expe_id) FROM experiment WHERE rf_id=?",
                          (current_rf_id,) )
            expe_id, = dbcur.fetchone()
            if expe_id is None:
                self.logger.info(
                  "No experiments have been performed with this receptive field, rf_id=%d"
                   % current_rf_id)
                self.current_id = 0
            else:
                self.current_id = expe_id
                self.set_everything( self.current_id )
                self.logger.debug(
                   'Set up experiment panel with expe_id=%d found in DB' 
                   % self.current_id )
        else:
            # just set up the page with the values corresponding to the expe_id passed
            self.current_id = expe_id
            self.set_everything( self.current_id )
            self.logger.debug('Set up experiment panel with rf_id=%d passed'
                              % self.current_id )
        self.manage_prev_next_state()

    def set_everything(self, expe_id):
        '''
        Should not be run directly, this is just a helper function for self.set_everything_from_database()
        Knowing the rf id number, set all the values for all the receptive field and load images.
        '''
        self.logger.debug('setting everything with expe_id = %d' % expe_id)
        # retreve the experiment table information.
        dbcur.execute("SELECT time, note FROM experiment WHERE expe_id == ?", (expe_id,))
        expe_time, expe_note = dbcur.fetchone()
        self.logger.debug("Found experiment with time = %s, and note = %s" % (expe_time, expe_note))
        stim_type, settings = self.get_settings_from_database(expe_id)
        self.select_stim(stim_type)
        self.settings_grid.clear_values()
        self.settings_grid.set_values(settings)
        # set the note.
        if expe_note != None:
            self.note_text_ctrl.SetValue(expe_note)
        else:
            self.note_text_ctrl.SetValue('')

    def SliderUpdate(self, event):
        # change the opacity setting on the eye_cup and rf objects 
        self.bg_shade = self.bg_color_slider.GetValue()/2.0
        self.ec_opacity = self.trans_slider.GetValue()/100.0

        # set up the rf for drawing
        rf = self.parent.chalk_panel.rf_panel.rf
        rf.update_opacity(self.ec_opacity)
        rf.update_bg_shade(self.bg_shade)

        # set up the eye_cup for drawing
        eye_cup = self.parent.sess_panel.eye_panel.eye_cup
        eye_cup.update_opacity(self.ec_opacity)
        eye_cup.update_bg_shade(self.bg_shade)
        
        # draw these onto the screen
        for i in xrange(2):
            eye_cup.draw()
            rf.draw()
            self.stimulus_window.flip()
        if event is not None:
            event.Skip()

    def OnBackground(self, event):
        self.SliderUpdate(None)
        if event is not None:
            event.Skip()


    def OnChange(self, event):
        if self.SelectedStimIsTriggerable:
            # the user changed something in a grid cell, so we should recalculate the runtime of the stimulus.
            self.logger.debug("A grid cell value has changed... recalculating the runtime of this stimulus.")

            # retrieve the new grid sttings for this stimulus
            grid_names, grid_values, grid_descriptions = self.settings_grid.get_everything(validated=True)

            # find out how long it needs to run, with these settings.
            exec("from stimuli.%s import %s_runtime" % (self.SelectedStim, self.SelectedStim))
            runtime = eval('%s_runtime(*grid_values)' % (self.SelectedStim))

            # update the text on the button
            old_label = self.run_button.GetLabelText()
            tokens = old_label.split('=')
            self.run_button.SetLabel( tokens[0] + "= %d seconds" % runtime)
            self.run_sizer.Layout()
            self.sizer.Layout()
        if event is not None:
            event.Skip()

    def OnSubmit(self, event):
        note = self.note_text_ctrl.GetValue()
        if note == '':
            note = None # submit None to database if note is empty
        self.logger.debug('Attaching note to current experiment expe_id=%d' % self.current_id)
        # submit the current note, to the current experiment.
        dbcur.execute('UPDATE experiment SET note=? WHERE expe_id==?',
                          (note, self.current_id) )

        # make changes to database perminant
        if not debug_mode:
            dbcon.commit()
            self.logger.info('Committed all changes to the database %s/%s.' % (database_path, database_name) )

    def OnPreview(self, event):
        self.logger.debug('Preview button pressed')
        self.run_stimulus()
        if event is not None:
            event.Skip()

    def run_stimulus(self, preview=True):
        self.logger.info("Running stimulus named %s with preview=%s" % (self.SelectedStim, preview))

        # get the current eye_cup information from the database
        sess_id = self.parent.sess_panel.current_id
        dbcur.execute('SELECT eyec_id FROM session WHERE sess_id=?',
                      (sess_id,) )
        try:
            eyec_id, = dbcur.fetchone()
        except:
            self.logger.critical("No eyecup information, you must run 'set' on the session panel and submit changes.")
            raise RuntimeError("No eyecup information, you must run 'set' on the session panel and submit changes.")
        self.logger.debug( "found that current eyec_id should be: %d" % eyec_id )
        eye_cup = self.parent.sess_panel.eye_panel.eye_cup

        # get the current rf information from the database.
        rf_id = self.parent.chalk_panel.current_id
        if rf_id == 0:
            self.logger.critical("You must submit a receptive field before running experiments.")
            raise RuntimeError("You must submit a receptive field before running experiments.")
        rf = self.parent.chalk_panel.rf_panel.rf

        # get information from the setting_grid
        values = tuple(self.settings_grid.get_values())
        #print values
        # import the code to run the stimulus
        exec( "import stimuli.%s as %s" % (self.SelectedStim, self.SelectedStim) )
        self.logger.debug( "import stimuli.%s as %s" % (self.SelectedStim, self.SelectedStim))
        kwargs = {'window':self.stimulus_window, 'preview':preview, 'eye_cup':eye_cup, 'rf':rf}
        exec( "times = %s(*values, **kwargs)" % self.SelectedStim )
        self.times = times

        if self.vft_check.IsChecked() and self.vft_check.IsEnabled():
            from stimuli.helper_codes.analyze_frame_times import analyze_frame_times as aft
            aft(times)

        # reset the background to its previous state.
        self.SliderUpdate(None)

    def set_up_stimulus_window(self):
        """
        Set up a psychopy.visual Window to pass to stimuli later when they're run.
        """

        # get the current rf information from the database.
        rf_id = self.parent.chalk_panel.current_id
        if rf_id == 0:
            self.logger.critical("You must submit a receptive field before running experiments.")
            raise RuntimeError("You must submit a receptive field before running experiments.")
        rf = self.parent.chalk_panel.rf_panel.rf

        self.stimulus_window = setup_window(None, win_size, None, rf, False, 0,0,0.0)
        self.logger.debug( 'set up a stimulus window' )

        rf = self.parent.chalk_panel.rf_panel.rf
        rf.prep_for_drawing(self.stimulus_window, reverse_rf=True)
        eye_cup = self.parent.sess_panel.eye_panel.eye_cup
        eye_cup.prep_for_drawing(self.stimulus_window, rf=rf)


    def get_settings_from_database(self, expe_id):
        '''
        Return a list of (name, value) tuples for the relevant settings of the stimulus used during
            the experiment with expe_id supplied.  Also, return the type of the stimulus delivered.
        Inputs:
            expe_id     : the experiment id for the database lookup.
        Returns:
            stim_type          : a string name of the stimulus
            settings           : a set of lists suitable to send to settings_grid.set_values
        '''
        self.logger.debug('Getting setting_values from database')
        dbcur.execute("""SELECT type, name, setting.description, value, sv_id 
                         FROM stimulus INNER JOIN setting ON stimulus.stim_id==setting.stim_id 
                         INNER JOIN setting_value         ON setting.sett_id==setting_value.sett_id 
                         WHERE expe_id==? ORDER BY sv_id""", (expe_id,))

        setting_values       = List_dict() 
        setting_names        = [] 
        setting_descriptions = []
        
        for row in dbcur:
            stim_type, setting_name, setting_description, setting_value, sv_id = row
            setting_values[setting_name] = setting_value
            if setting_name not in setting_names:
                setting_names.append(setting_name)
                setting_descriptions.append(setting_description)

        # we need to make a list of rows [name, description, value] to send to the settings_grid
        settings = [[setting_names[i], setting_descriptions[i], setting_values[setting_names[i]]]\
                       for i in xrange(len(setting_names))]

        return stim_type, settings

    def OnConvert(self, event):
        deg = float(self.deg_text_ctrl.GetValue())
        converted_value = self.calc_deg_to_rf(deg)
        self.deg_text.SetLabel("%3.2f degrees is %3.2f in RF units." %(deg,converted_value))
        
    def OnRun(self, event):
        self.logger.debug("Run experiment with these options button pressed")
        # run the stimulus with preview=False
        self.run_stimulus(preview=debug_mode)
        expe_time = wx.DateTime().Now().GetJDN()
        # look for data in the /experiment_data/upload_data directory.
        self.logger.debug('Looking for data to store')
        # this dialog will find out the name of the new file and put that in self.data_filename, if they click
        #    cancel it will put None in self.data_filename instead
        dbcur.execute('SELECT MAX(expe_id) FROM experiment')
        max_expe = dbcur.fetchone()[0]
        if max_expe is not None:
            expe_num = max_expe + 1
        else:
            expe_num = 1
        dlg = Find_data_dialog(self, wx.ID_ANY, expe_num, expe_time, style=wx.STAY_ON_TOP)
        dlg.ShowModal()
        if event is not None:
            event.Skip()

    def submit_experiment_to_database(self, expe_time):
        # submit everything to the database.
        # -- first, submit a new experiment, with the current time and possibly a note.
        rf_id = self.parent.chalk_panel.current_id
        sess_id = self.parent.sess_panel.current_id
        if self.note_check_box.IsChecked():
            # if we're supposed to link the note to the experiment, then we do so.
            note = self.note_text_ctrl.GetValue()
            dbcur.execute('INSERT INTO experiment (sess_id, time, note, rf_id, bg_shade) VALUES (?,?,?,?,?)',
                          (sess_id, expe_time, note, rf_id, self.bg_shade) )
        else:
            # otherwise it's just the experiment time and session_id we care about.
            dbcur.execute('INSERT INTO experiment (sess_id, time, rf_id, bg_shade) VALUES (?,?,?,?)',
                          (sess_id, expe_time, rf_id, self.bg_shade) )
        # now get the expe_id of what we just inserted.
        dbcur.execute('SELECT MAX(expe_id), time, note, rf_id, bg_shade FROM experiment')
        expe_id, time, note, rf_id, bg_shade = dbcur.fetchone()
        self.logger.debug(
               "Just added experiment with expe_id: %d, time: %f, note:%s, rf_id: %d, bg_shade: %1.2f" %
               (expe_id, time, note, rf_id, bg_shade))
        self.current_rf_id = rf_id

        # -- find out if we need a new stimuli entry or not
        # ---- first determine the current revision of the stimuli to be run.
        svnlog = os.popen("svn info stimuli/%s.py" % self.SelectedStim)
        lines  = svnlog.readlines()
        for l in lines:
            if 'Last Changed Rev:' in l:
                tokens = l.split(':') # split on colon
                svnrev = int(tokens[1])
                self.logger.debug('Stimulus [[%s]] last changed at revision: %d' %\
                       (self.SelectedStim, svnrev))
        dbcur.execute('SELECT stim_id FROM stimulus WHERE svn_revision=? AND type=?',
                      (svnrev, self.SelectedStim))
        rows = dbcur.fetchall()
        if len(rows) > 0:
            # we've found this stimulus in the database, and don't need to insert
            # it again, all we need is it's stim_id
            stim_id, = rows[-1]
            self.logger.debug("Found that this stimulus was already in database with stim_id: %d" %\
                    stim_id)
        else:
            # we couldn't find this stimulus, so we'll add it.
            self.logger.debug("Adding a stimulus to the database with name: %s and revision: %d" %\
                  (self.SelectedStim, svnrev))
            dbcur.execute('INSERT INTO stimulus (type, svn_revision) VALUES (?,?)',
                          (self.SelectedStim, svnrev))
            dbcur.execute('SELECT MAX(stim_id), type, svn_revision FROM stimulus')
            stim_id, stim_type, svnrev = dbcur.fetchone()
            self.logger.debug("Stimulus with ID:%d  name:%s  revision:%d  being used." %\
                (stim_id, stim_type, svnrev))
        # -- Using this stim_id, see if we need to submit the settings
        grid_names, grid_values, grid_descriptions = self.settings_grid.get_everything()
        dbcur.execute('SELECT name, sett_id FROM setting WHERE stim_id=?', (stim_id,))
        setting_names = []
        sett_ids      = []
        for row in dbcur:
            setting_names.append(row[0])
            sett_ids.append(     row[1])
        self.logger.debug("Initially found the following settings for (in database already) stim_id: %d, %s with ids %s" %\
               (stim_id, str(setting_names), str(sett_ids)))
        for gname in grid_names:
            if gname not in setting_names:
                # we need to add the grid_name to the setting table and get its id
                dbcur.execute('INSERT INTO setting (stim_id, name, description) VALUES (?,?,?)',
                              (stim_id, gname, grid_descriptions[grid_names.index(gname)]))
                dbcur.execute('SELECT MAX(sett_id), stim_id, name, description FROM setting')
                sett_id, stim_id, name, description = dbcur.fetchone()
                self.logger.debug("Submitted entry into settings table: sett_id=%d stim_id=%d name=%s, description=%s" %\
                    (sett_id, stim_id, name, description))
                # at this point all the settings are up to date in the database.
        # since we may have added to the settings table, find out the names and ids for all the settings for this stim.
        dbcur.execute('SELECT name, sett_id FROM setting WHERE stim_id=?', (stim_id,))
        setting_names = []
        sett_ids      = []
        for row in dbcur:
            setting_names.append(row[0])
            sett_ids.append(     row[1])
        self.logger.debug("Finally found the following settings for (in database already) stim_id: %d, %s with ids %s" %\
               (stim_id, str(setting_names), str(sett_ids)))

        # we need a way to relate the sett_id to the name, so lets construct a dictionary.
        sett_dict = {}
        for i in enumerate(sett_ids):
            sett_dict[setting_names[i[0]]] = i[1]
        
        # we now have all the sett_ids so, lets insert the settings_values into the database
        # -- check to see if the settings are lists or just single numbers
        # -- if lists, insert each element individually
        for i in enumerate(grid_descriptions):
            if '[list]' in i[1]:
                # this setting is a list, so we'll submit a database entry for each element.
                for v in grid_values[i[0]]:
                    dbcur.execute('INSERT INTO setting_value (sett_id, expe_id, value) VALUES (?,?,?)',
                                    (sett_dict[grid_names[i[0]]], expe_id, v))
            else: # it's just a normal value so submit it normally
                dbcur.execute('INSERT INTO setting_value (sett_id, expe_id, value) VALUES (?,?,?)',
                                (sett_dict[grid_names[i[0]]], expe_id, grid_values[i[0]]))
            self.logger.debug("setting %s=%s submitted to database with sett_id=%d and expe_id=%d" %\
                    (str(grid_names[i[0]]), str(grid_values[i[0]]), sett_dict[grid_names[i[0]]], expe_id))
        self.current_id = expe_id
        self.manage_prev_next_state()

        # make changes to database perminant
        if not debug_mode:
            dbcon.commit()
            self.logger.info('Committed all changes to the database %s/%s.' % (database_path, database_name) )

    def archive_data(self, filename):
        if filename != None:
            self.logger.debug('archiving data')
            # find out what we should rename the data file to.
            new_name = '%d-%s-' % (self.current_id, self.SelectedStim)
            grid_names, grid_values, grid_descriptions = self.settings_grid.get_everything()
            sess_id = self.parent.sess_panel.current_id
            for n,v in zip(grid_names,grid_values):
                strv = ""
                if type(v) == type([]):
                    for val in v:
                        strv += "%3.1f, " % val
                    # remove the last comma and space
                    strv = strv[:-2]
                else:
                    strv = "%3.1f" % v
                if n == "refresh_rate" or n == "win_size":
                    pass # don't save win size or refresh rate... filenames are too long
                else:
                    new_name += '-%s %s' % (n,strv)
            if len(new_name) > 256:
                new_name = new_name[:256]
            if debug_save_mode:
                t = wx.DateTime.Today()
                debug_folder = '/experiment_data/debug/%d-%d-%d/session_%d' % (t.GetMonth()+1, t.GetDay(), t.GetYear(), sess_id)
                os.system('mkdir -p %s' % debug_folder )
                os.system('cp -f eye_cup.jpg "%s/eye_cup.jpg"' % debug_folder)
                os.system('cp -f eye_cup.txt "%s/eye_cup.txt"' % debug_folder)
                # extend debug_folder to include the rf_id
                debug_folder += "/rf-%d" % self.current_rf_id
                os.system('mkdir -p %s' % debug_folder )
                os.system('cp -f "/experiment_data/upload_data/%s" "%s/%s"' % (filename, debug_folder, new_name))
                os.system('cp -f rf.txt %s/rf.txt' % debug_folder)
                os.system('cp -f /experiment_data/stored_data/receptive_field_images/%d/*.jpg %s' % (self.current_rf_id, debug_folder) )
                cPickle.dump(self.times, open(debug_folder + "/%d-times.cPickle" % self.current_id, 'w'))
            new_folder = '/experiment_data/stored_data/session_%d/experiment_%d' %\
                      (sess_id,self.current_id)
            os.system('mkdir -p %s' % new_folder )
            os.system('mv -f "/experiment_data/upload_data/%s" "%s/%s"' % (filename, new_folder, new_name))
            cPickle.dump(self.times, open(new_folder + "/%d-times.cPickle" % self.current_id, 'w'))
        else:
            self.logger.debug('no data to archive')
        

    def OnPrevious(self, event):
        ''' if possible, set all values in this panel to previous entry in database '''
        # set all values
        self.logger.debug("Hit Previous button")
        self.current_id = self.prev_expe_id
        self.set_everything(self.current_id)

        self.manage_prev_next_state()
        if event is not None:
            event.Skip()
             

    def OnNext(self, event):
        ''' If possible, set all values in this panel to NEXT entry in database. '''
        self.logger.debug("Hit Next button")
        self.current_id = self.next_expe_id
        self.set_everything(self.current_id)
            
        self.manage_prev_next_state()
        if event is not None:
            event.Skip()


    def manage_prev_next_state(self):
        ''' determine the enable/disabled state of previous and next buttons. '''
        self.logger.debug('Managing previous/next buttons state')
        # determine the all the expe_ids in the database for the current rf_id
        dbcur.execute( "SELECT expe_id FROM experiment WHERE rf_id=?", 
                       (self.parent.chalk_panel.current_id,) )
        rows = dbcur.fetchall()
        if len(rows) > 0:
            self.expe_ids = [rows[i][0] for i in xrange(len(rows))]
            self.expe_ids.sort()
        else:
            self.expe_ids = []

        # find the next highest and next lowest sess_id from the current_sess_id
        self.prev_expe_id = 0
        self.next_expe_id = 0
        #print self.current_id
        #print self.expe_ids
        for e in self.expe_ids:
            if e < self.current_id:
                self.prev_expe_id = e
            if e > self.current_id:
                self.next_expe_id = e
                break
        
        # potentially disable next button
        if self.next_expe_id == 0:
            self.next_button.Disable()
        else:
            self.next_button.Enable()

        # potentially disable prev button
        if self.prev_expe_id == 0:
            self.prev_button.Disable()
        else:
            self.prev_button.Enable()

        # find out how long it needs to run, with these settings.
        self.logger.debug("calculating Runtime for %s" % self.SelectedStim)
        grid_names, grid_values, grid_descriptions = self.settings_grid.get_everything()
        exec("from stimuli.%s import %s_runtime" % (self.SelectedStim, self.SelectedStim))
        runtime = eval('%s_runtime(*grid_values)' % (self.SelectedStim))
        # update the text on the button
        old_label = self.run_button.GetLabelText()
        tokens = old_label.split('=')
        self.run_button.SetLabel( tokens[0] + "= %d seconds" % runtime)
        self.run_sizer.Layout()
        self.sizer.Layout()
        
    def OnStim(self, event):
        self.logger.debug('Stimulus type=%s selected.' % event.GetString())
        self.select_stim(event.GetString())
        if event is not None:
            event.Skip()

    def select_stim(self, stim_type):
        # FIXME this should take into acount the subversion repository revision of stim_type
        self.logger.debug('Preparing settings_grid for stim_type=%s' % stim_type)
        self.SelectedStim = stim_type
        self.stim_choice.SetStringSelection(self.SelectedStim)
        # retrieve the settings for this stimulus
        exec("from stimuli.%s import %s_settings" % (self.SelectedStim, self.SelectedStim))
        settings = eval('%s_settings()' % (self.SelectedStim))
        # set the default settings in the self.settings_grid
        self.settings_grid.clear_values()
        for s in settings:
            if type(s) == type(True):
                self.SelectedStimIsTriggerable = s
            else:
                # this setting is normal, so put its values in the grid normally
                self.settings_grid.set_values(s)

        self.sp_gbs.Layout()
        self.grid_scroller.SetupScrolling(scroll_x=False, scrollToTop=False)
        self.settings_grid.SetGridCursor(0,1)
        if self.SelectedStimIsTriggerable:
            self.run_button.Enable()
            self.vft_check.Enable()
        else:
            self.run_button.Disable()
            self.vft_check.Disable()
        # calculate and update the runtime on the run button.
        self.OnChange(None) # arguement is ignored by function.

    
    def find_stimuli(self):
        '''
        This function tells you what stimuli are available to be shown and their
            respective settings.
        '''
        self.logger.debug('Finding out what kinds of stimuli are available.')
        # find all the stimulus defining files.
        stim_names = []
        files = os.listdir('stimuli')
        for file in files:
            if '.py' == file[-3:] and "__init__" not in file and 'chalk' not in file:
                stim_names.append(file[:-3]) # cut off the .py
        return stim_names
        

# ===========================================================================================================
#                                                 Run_exp_notebook
# ===========================================================================================================
class Run_exp_notebook(wx.Notebook):
    def __init__(self, parent, **kwargs):
        self.parent = parent
        wx.Notebook.__init__(self, parent, **kwargs)
        self.logger = logging.getLogger('Run_experiment')

        # as we set up the notebook, check to see if the user wants to set it up clean or from the latest
        #    database entries
        new_or_old_dialog = New_or_old_dialog(self, wx.ID_ANY, style=wx.STAY_ON_TOP)
        # this dialog will set self.initial_spec_id to 0 or -1 for the options above respecively.
        new_or_old_dialog.ShowModal()

        if not self.user_canceled: # set by dialog
            self.page = [{},{},{},{}]
            self.spec_panel = Spec_panel(self, wx.ID_ANY)
            self.AddPage(self.spec_panel, "Specimin")
            self.spec_panel.set_everything_from_database(self.initial_spec_id)

            self.sess_panel = Sess_panel(self, wx.ID_ANY)
            self.AddPage(self.sess_panel, "Session")

            self.chalk_panel = Chalk_panel(self, wx.ID_ANY)
            self.AddPage(self.chalk_panel, "Chalk RF")

            self.expe_panel = Expe_panel(self, wx.ID_ANY)
            self.AddPage(self.expe_panel, "Experiment")

            self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.OnPageChanged)
            self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGING, self.OnPageChanging)

            self.page[0] = {'panel':self.spec_panel,   'panel_is_setup':False}
            self.page[1] = {'panel':self.sess_panel,   'panel_is_setup':False}
            self.page[2] = {'panel':self.chalk_panel,  'panel_is_setup':False}
            self.page[3] = {'panel':self.expe_panel,   'panel_is_setup':False}

    def OnPageChanged(self, event):
        old = event.GetOldSelection()
        new = event.GetSelection()
        sel = self.GetSelection()
        event.Skip()

    def OnPageChanging(self, event):
        old = event.GetOldSelection()
        new = event.GetSelection()
        sel = self.GetSelection()
        # when closing the window an event gets passed with old = -1, just ignore it.
        if old == -1:
            event.Skip()
            return
        self.logger.debug("Changing from page %d to page %d" % (old, new) )

        # check to see if old panel has been altered from its database values.
        if self.page[old]['panel'].panel_is_altered():
            dlg = Altered_panel_confirmation_dialog(self, wx.ID_ANY)
            dlg.ShowModal()
            if self.action_choice == 'Continue':
                # re-update the panel with information from the database
                self.page[old]['panel'].reset_everything_to_database()
            else:
                # user hit cancel, so veto the page change event.
                event.Veto()
                return # don't do anything else
        
        # setup page beyond the one we're leaving
        for tpage in self.page[old+1:]: # for every page beyond the old one
            if not tpage['panel_is_setup']:
                tpage['panel'].set_everything_from_database(-1) # set up the panel from the latest entry in database.
                # if the panel just before this one has a current_id of 0, then disable this panel
                if self.page[self.page.index(tpage)-1]['panel'].current_id == 0:
                    tpage['panel'].Disable()
                else:
                    tpage['panel'].Enable()
                tpage['panel_is_setup'] = True # keep track that this panel has been set up
        if new == 3: # we're changing TO the experiment panel
            # set up the stimulus window right away
            self.expe_panel.set_up_stimulus_window()
            self.expe_panel.OnBackground(None)
            if self.chalk_panel.current_id != 0:
                self.expe_panel.Enable()
            else:
                self.expe_panel.Disable()
        if old == 3: # we're changing FROM the experiment panel
            if hasattr(self.expe_panel, 'stimulus_window'):
                self.expe_panel.stimulus_window.close()
        event.Skip()

# Run the application
if __name__ == '__main__':

    import sys
    for arg in sys.argv:
        if '--debug' in arg:
            debug_mode = True
    # ------ SET UP LOGGERS ---------------------------

    LOG_FILENAME = 'logfiles/Run_experiment.log'

    # Set up a specific logger with our desired output level
    rexp_logger = logging.getLogger('Run_experiment')
    rexp_logger.setLevel(logging.DEBUG)

    # Add the log message handler to the logger
    handler = logging.handlers.RotatingFileHandler(
                  LOG_FILENAME, maxBytes=1000000, backupCount=5)

    # create formatter
    formatter = logging.Formatter("%(levelname)s|%(asctime)s[%(name)s]: %(message)s")
    handler.setFormatter(formatter)

    rexp_logger.addHandler(handler)

    # create console handler and set level to debug
    spit = logging.StreamHandler()
    if debug_mode:
        spit.setLevel(logging.DEBUG)
    else:
        spit.setLevel(logging.INFO)
    # create formatter
    spit_formatter = logging.Formatter("%(levelname)s[%(name)s]: %(message)s")
    # add formatter to ch
    spit.setFormatter(spit_formatter)
    # add ch to logger
    rexp_logger.addHandler(spit)
    # -------------------------------------------------


    # open up and connect to the database.
    database_name = 'database.sqlite'
    database_path = '/experiment_data/stored_data'

    files = os.listdir(database_path)

    if database_name in files:
        dbcon = sqlite3.connect(os.path.join(database_path, database_name) )
        dbcur = dbcon.cursor()
        rexp_logger.debug( 'Connected to the database %s/%s' % (database_path, database_name) )
        if debug_mode: 
            rexp_logger.debug('   DEBUG MODE (no database submissions will be committed)')
    else:
        rexp_logger.critical( "Couldn't connect to the database %s/%s... exiting." 
                              % (database_path,database_name) )
        raise RuntimeError("Couldn't connect to the database %s/%s" 
                           % (database_path, database_name) )

    # start the gui application
    app = wx.PySimpleApp()
    frame = wx.Frame(None, title="Run_Experiment", size=(900,700))
    notebook = Run_exp_notebook(frame)
    frame.Show()
    app.MainLoop()
    
    # close the database
    if not debug_mode:
        dbcon.commit()
        rexp_logger.info('Committed all changes to the database %s/%s.' % (database_path, database_name) )
    dbcur.close()
    dbcon.close()




